欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android?ASM插樁探索實戰(zhàn)詳情

 更新時間:2022年09月05日 08:53:36   作者:孝之請回答  
這篇文章主要介紹了Android?ASM插樁探索實戰(zhàn)詳情,文章圍繞主題展開詳細的內(nèi)容戒殺,具有一定的參考價值,需要的小伙伴可以參考一下

前言

我們都知道,在Android編譯過程中,Java代碼會被編譯成Class文件,Class文件再被打進Dex文件,虛擬機最終會加載Dex文件去執(zhí)行。 

 插樁,就是干涉代碼的編譯過程,在編譯期間生成新的代碼或者修改已有的代碼。

常用的EventBus、ARouter,內(nèi)部就是使用了APT(Annotation Process Tool),在編譯的最開始解析Java文件中的注解,并生成新的Java文件。

但如果有以下兩個需求:

  • 給Activity的attach()方法加日志
  • 將第三方庫中所有調(diào)用getDeviceId()的地方替換為我們自己的方法,使其符合隱私規(guī)范

這兩個需求一個是需要修改Android SDK的Activity文件,一個是需要修改三方庫中的某個方法。而我們集成它們的方式是通過Jar包/AAR,本質(zhì)上也就是Class文件。這時候就需要我們能夠在編譯階段去修改Class文件,這也就是ASM發(fā)揮作用的地方。

通過本文,你可以解決如下問題:

  • ASM的作用是什么?
  • 如何使用ASM?
  • 如何將ASM運用到我們的實際項目中來?

ASM的作用是什么?

在介紹ASM插樁之前,首先來回顧一下Java Class文件。在AS中,我們可以看到打開一個Class文件是這樣的:

但其實這是IDE為了方便開發(fā)者查閱,特意解析渲染了CLASS文件。如果直接拖進編輯器查看這個文件的話,我們可以看到它其實是這樣的:

上圖是CLASS文件的16進制代碼。一般人都看不懂這些代碼的含義...但既然AS可以將這些代碼解析成開發(fā)者可以看懂的樣子,說明CLASS文件肯定是遵循某個格式規(guī)范的。所以,一個熟悉CLASS文件格式規(guī)范的開發(fā)者,是完全有能力解析所有的CLASS文件,甚至修改CLASS文件的。

ASM的開發(fā)者就是這么做的,并且提供一套完整的API幫助我們在不需要了解CLASS文件格式規(guī)范的情況下,可以解析并修改CLASS文件

如何使用ASM?

基本使用方式

下面我們就來使用一下ASM,看一下它能達到的效果。假設(shè)現(xiàn)在我們需要統(tǒng)計MainActivity所有方法的耗時,原先的MainActivity.Class文件是這樣的:

用ASM修改過后的MainActivity.Class文件:

 具體的實現(xiàn)代碼:

// 讀取Class文件
String clazzFilePath = "/Users/xiaozhi/AndroidStudioProjects/ASMTest/app/build/intermediates/javac/debug/classes/com/xiaozhi/asmtest/MainActivity.class";
ClassReader classReader = new ClassReader(new FileInputStream(clazzFilePath));
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
MethodTimeConsumeClassVisitor methodTimeConsumeClassVisitor = new MethodTimeConsumeClassVisitor(Opcodes.ASM5, classWriter);
classReader.accept(methodTimeConsumeClassVisitor, ClassReader.SKIP_FRAMES);

// 寫入Class文件
byte[] bytes = classWriter.toByteArray();
FileOutputStream fos = new FileOutputStream(clazzFilePath);
fos.write(bytes);
fos.flush();
fos.close();

首先在第6行,通過ClassReader.accept(classVisitor, parsingOptions)讀取Class文件。然后將修改完的字節(jié)碼用FileOutputStream寫回原文件,原先的Class代碼也就被修改了。但這里我們看不到是怎么修改的,因為修改其實就發(fā)生在讀取階段,ClassReader負責(zé)讀取解析Class文件,遇到相應(yīng)節(jié)點后,調(diào)用ClassVisitor中的方法去修改相應(yīng)的節(jié)點代碼(4、5行)。

這里涉及到兩個類,ClassWriter與MethodTimeConsumeClassVisitor,這兩個類都繼承于ClassVisitor。結(jié)合第9行我們可以猜測,ClassWriter肯定可以記錄我們修改后的字節(jié)碼。既然ClassWriter是用來記錄的,而第6行ClassReader.accept(classVisitor, parsingOptions)讀取Class文件又只能接收一個classVisitor,那我們怎么用另一個ClassVisitor去修改Class文件呢?

我們可以看到ClassVisitor有這么一個構(gòu)造函數(shù):

public ClassVisitor(final int api, final ClassVisitor classVisitor)

所以我們第5行的代碼,實際上是用自定義的ClassVisitor-MethodTimeConsumeClassVisitor,代理了ClassWriter,在需要修改的Class節(jié)點復(fù)寫方法進行修改就可以了。

另外我們額外了解一下構(gòu)造函數(shù)中的幾個參數(shù)。

// 接收Flag參數(shù),用于設(shè)置方法的操作數(shù)棧的深度。COMPUTE_MAXS可以自動幫我們計算stackSize。
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);

// 接收api與ClassVisitor。 Opcodes.ASM4~Opcodes.ASM9標(biāo)識了ASM的版本信息
MethodTimeConsumeClassVisitor methodTimeConsumeClassVisitor = new MethodTimeConsumeClassVisitor(Opcodes.ASM5, classWriter);

// 接收ClassVisitor與parsingOptions參數(shù)。 parsingOptions用來決定解析Class的方式,SKIP_FRAMES代表跳過MethodVisitor.visitFrame方法
classReader.accept(methodTimeConsumeClassVisitor, ClassReader.SKIP_FRAMES);

自定義ClassVisitor

下面我們具體看一下怎么通過自定義ClassVisitor修改Class文件。

public class MethodTimeConsumeClassVisitor extends ClassVisitor {
    private String mOwner;

    public MethodTimeConsumeClassVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }
    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        mOwner = name;
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
        return new TimeConsumeMethodVisitor(mOwner, api, methodVisitor, access, name, descriptor);
    }
}

我們可以看到第9行與第15行,分別是visit方法與visitMethod方法,對應(yīng)的是訪問Class文件頭部與Class文件方法這兩個節(jié)點。

類似的還有很多節(jié)點:

visit visitSource? visitOuterClass? ( visitAnnotation |
   visitAttribute )*
   ( visitInnerClass | visitField | visitMethod )*
   visitEnd

我們要統(tǒng)計MainActivity所有方法的耗時,就需要重寫visitMethod方法。第15行的visitMethod返回了一個MethodVisitor,顧名思義就是用來遍歷修改Method,跟ClassVisitor是一個道理只不過維度不同罷了。類似的還有AnnotationVisitor與FiledVisitor,它們分別在visitAnnotation和visitField方法中返回,用來訪問修改注解與字段。

然后我們來看這個MethodVisitor是怎么修改方法的:

static class TimeConsumeMethodVisitor extends AdviceAdapter {
    private final String methodName;
    private final int access;
    private final String descriptor;
    private final String owner;

    private static final String METHOD_TIME_CONSUME_LOG_TAG = "METHOD_TIME_CONSUME_ASM_HOOK";
    private static final String METHOD_TIME_CONSUME_LOG = "method time consume:";

    protected TimeConsumeMethodVisitor(String owner, int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
        super(api, methodVisitor, access, name, descriptor);
        this.owner = owner;
        this.methodName = name;
        this.access = access;
        this.descriptor = descriptor;
    }

    @Override
    protected void onMethodEnter() {
        System.out.println("TimeConsumeMethodVisitor onMethodEnter. clazzName:" + owner + ", methodName:" + methodName + ", access:" + access + ", descriptor:" + descriptor);

        visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        visitVarInsn(LSTORE, 1);

        super.onMethodEnter();
    }

    @Override
    protected void onMethodExit(int opcode) {
        System.out.println("TimeConsumeMethodVisitor onMethodExit. clazzName:" + owner + ", methodName:" + methodName + ", access:" + access + ", descriptor:" + descriptor);

        visitLdcInsn(METHOD_TIME_CONSUME_LOG_TAG);
        visitTypeInsn(NEW, "java/lang/StringBuilder");
        visitInsn(DUP);
        visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
        visitLdcInsn(METHOD_TIME_CONSUME_LOG);
        visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        visitVarInsn(LLOAD, 1);
        visitInsn(LSUB);
        visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
        visitLdcInsn("ms" + ", clazz:" + owner + ", method:" + methodName);
        visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
        visitMethodInsn(INVOKESTATIC, "com/taobao/yyds/common/utils/Logger", "d", "(Ljava/lang/String;Ljava/lang/String;)V", false);
        super.onMethodExit(opcode);
    }
}

在第19行onMethodEnter()方法中,我們實現(xiàn)了

long startTime = System.currentTimeMillis();

在第29行onMethodExit()方法中,我們實現(xiàn)了

Logger.d("METHOD_TIME_CONSUME_ASM_HOOK", "method time consume:" + (System.currentTimeMillis() - var1) + "ms, clazz:com/xiaozhi/asmtest/MainActivity, method:");

至于其中所用的API,我們可以通過函數(shù)名大致推斷出什么意思,感興趣的話可以去學(xué)習(xí)這些API的使用。但因為剛?cè)腴T,我這里使用了一個偷懶的方式:ASM ByteCode Viewer。

ASM ByteCode Viewer

ASM ByteCode Viewer是專門用于ASM插樁的AS插件。

安裝該插件后,我們可以很方便地查看一個Class文件怎么用ASM的API去寫出來:

 如果我想很快速地知道如何通過ASM API去給方法加耗時日志,只需要先在本地Java文件中寫好這一段邏輯,然后通過插件查看對應(yīng)的API是怎么樣的就可以了。但建議還是要多了解下這些API,因為我們寫的Java代碼可能不是通用的,在其它Java文件中不一定就能順利地編譯成功,因此往往會有需要進行適配的地方,排查的過程中就需要我們了解API才行了。

到這里,ASM的基本使用就已經(jīng)講好了。如果感興趣可以參考官方文檔asm.ow2.io/asm4-guide.…去實踐。

如何將ASM運用都我們的實際項目中來?

上一節(jié)我們已經(jīng)知道如何用ASM對一個Class文件進行修改,那么怎么運用到我們的項目中來呢?Android打包過程中會將Class文件打包成Dex文件,在這個階段我們可以借助AGP(Android Gradle Plugin)與Android Transform來遍歷訪問到所有需要的Class文件,再通過ASM去修改。

引入工程

Android Gradle Plugin

自定義插件一共分為5個步驟:

  • 創(chuàng)建插件項目
  • 配置插件
  • 實現(xiàn)插件
  • 發(fā)布插件
  • 應(yīng)用插件

創(chuàng)建插件項目

跟其它子模塊一樣,我們需要創(chuàng)建一個插件模塊,然后在根目錄的settings.gradle中引入該模塊。

配置插件

首先,插件模塊的文件目錄需要嚴格遵守以下目錄結(jié)構(gòu)(因為我們選擇用groovy實現(xiàn)插件,所以要用groovy文件夾):

main
├── groovy
├── resources 
    ├── META-INF
        ├── gradle-plugins
            ├── *.properties

上圖中的配置文件需要特別注意,該配置文件代表著插件id->插件實現(xiàn)類的映射。

 其它項目應(yīng)用插件時所用的插件id,就是配置文件的文件前綴com.yyds.asm.plugin。而實際的實現(xiàn)類就是com.xiaozhi.plugin.ASMPlugin。

另外,我們需要在build.gradle中配置如下內(nèi)容:

plugins {
    id 'groovy'
}

dependencies {
    implementation gradleApi()
    implementation localGroovy()
}

sourceSets {
    main {
        groovy {
            srcDir 'src/main/groovy'
        }

        resources {
            srcDir 'src/main/resources'
        }
    }
}

實現(xiàn)插件

接下來就是實現(xiàn)我們自定義的插件了,我們可以在ASMPlugin中寫我們需要執(zhí)行的邏輯:

class ASMPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        println("ASMPlugin apply")
    }
}

發(fā)布插件

插件項目寫完后,我們需要將其發(fā)布到maven倉庫中去(這里可以選擇先將其發(fā)布到本地maven倉庫),從而讓其它模塊可以方便地進行依賴。

我們需要在build.gradle中添加以下代碼:

uploadArchives {
    repositories {
        mavenDeployer {
            //設(shè)置插件的GAV參數(shù)
            pom.groupId = 'com.xiaozhi.plugin.asm'
            pom.artifactId = 'asmArt'
            pom.version = '1.0.1'
            //文件發(fā)布到下面目錄
            repository(url: uri('../maven_repo'))
        }
    }
}

sync后,我們就可以在gradle tasks中看到上傳插件的task:

執(zhí)行task,插件就會發(fā)布到本地的maven倉庫了,我們可以在本地的maven_repo文件夾中找到。

應(yīng)用插件

現(xiàn)在,我們的項目就可以很方便地依賴這個插件了。

只用做兩個步驟:

  1. 在工程根目錄build.gralde添加maven倉庫與插件依賴:
buildscript {
    repositories {
        ···
        maven { url uri('./maven_repo') }
    	···
    }
    dependencies {
        ···
        classpath "com.xiaozhi.plugin.asm:asmArt:1.0.1"
        ···
    }
}
  • 在想要依賴插件的項目的build.gradle中應(yīng)用插件:
plugins {
    id 'com.xiaozhi.plugin'
}

可以看到,這個id就是對應(yīng)的插件項目中配置文件的前綴名。

Android Transform

現(xiàn)在假設(shè)app模塊已經(jīng)應(yīng)用了我們的ASM插件,那么還需要使用Transform才能訪問到app模塊在編譯過程中產(chǎn)生/依賴的所有Class文件。

自定義一個Transform:

public class ASMTransform extends Transform {

    // transfrom名稱
    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }

    // 輸入源,class文件
    @Override
    public Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS;
    }

    // 文件范圍,整個工程
    @Override
    public Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT;
    }

    // 是否增量編譯,可用于編譯優(yōu)化
    @Override
    public boolean isIncremental() {
        return false;
    }

    // 核心方法
    @Override
    public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        
    }
}

我們主要看第29行transform()方法,在這里我們就能訪問到app模塊的所有Class文件。

實現(xiàn)如下:

@Override
public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
    super.transform(transformInvocation);
    if (!transformInvocation.isIncremental()) {
        //不是增量編譯刪除所有的outputProvider
        transformInvocation.getOutputProvider().deleteAll();
    }
    // 獲取輸入源
    Collection<TransformInput> inputs = transformInvocation.getInputs();
    inputs.forEach(transformInput -> {
        Collection<DirectoryInput> directoryInputs = transformInput.getDirectoryInputs();
        Collection<JarInput> jarInputs = transformInput.getJarInputs();
        directoryInputs.forEach(new Consumer<DirectoryInput>() {
            @Override
            public void accept(DirectoryInput directoryInput) {
                try {
                    // 處理輸入源
                    handleDirectoryInput(directoryInput);
                } catch (IOException e) {
                    System.out.println("handleDirectoryInput error:" + e.toString());
                }
            }
        });
        for (DirectoryInput directoryInput : directoryInputs) {
            // 獲取output目錄
            File dest = transformInvocation.getOutputProvider().getContentLocation(
                    directoryInput.getName(),
                    directoryInput.getContentTypes(),
                    directoryInput.getScopes(),
                    Format.DIRECTORY);
            //這里執(zhí)行字節(jié)碼的注入,不操作字節(jié)碼的話也要將輸入路徑拷貝到輸出路徑
            try {
                FileUtils.copyDirectory(directoryInput.getFile(), dest);
            } catch (IOException e) {
                System.out.println("output copy error:" + e.toString());
            }
        }

        for (JarInput jarInput : jarInputs) {
            // 獲取output目錄
            File dest = transformInvocation.getOutputProvider().getContentLocation(
                    jarInput.getName(),
                    jarInput.getContentTypes(),
                    jarInput.getScopes(),
                    Format.JAR);
            //這里執(zhí)行字節(jié)碼的注入,不操作字節(jié)碼的話也要將輸入路徑拷貝到輸出路徑
            try {
                FileUtils.copyFile(jarInput.getFile(), dest);
            } catch (IOException e) {
                System.out.println("output copy error:" + e.toString());
            }
        }
    });
}

這里的邏輯主要是三端:

  • 獲取輸入源

第9行獲取到的TransformInput中可以訪問到所有的DirectoryInput和JarInput,分別代表著我們項目中的Class文件與依賴的JAR包/AAR包中的Class文件。DirectoryInput和JarInput都繼承于QualifiedContent,調(diào)用getFile()方法就可以拿到Class文件的所有信息了。

  • 處理輸入源

獲取Class文件后,其實我們就可以用ASM去修改它了。相當(dāng)于把我們之前ASM修改Class文件的代碼復(fù)制過來就可以了,這一部分留到下一節(jié)中講。

  • 將輸入源文件拷貝到目標(biāo)文件中

處理完之后,我們還要記得把輸入源文件拷貝到輸出路徑中去,否則下一個transform可能就要失敗了,因為它找不到輸入源了。第27行transformInvocation.getOutputProvider().getContentLocation()可以確保我們獲取到最終的輸出路徑。

現(xiàn)在Transform寫好了,但我們還沒有應(yīng)用。應(yīng)用很簡單,只需要在插件中注冊一下就好了:

class ASMPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        def android = project.getExtensions().findByType(AppExtension)
        android.registerTransform(new ASMTransform())
    }
}

然后,重新發(fā)布插件到maven倉庫,sync一下,我們就可以在app模塊的gradle tasks中看到我們剛寫好的transform了:

至此,所有鏈路都已經(jīng)走通了,我們知道ASM如何修改Class文件,并可以利用AGP與Transfrom應(yīng)用到我們的工程中來。下面我們就用這條鏈路來實現(xiàn)一下方法節(jié)流。

方法節(jié)流

Android中最常見的方法節(jié)流就是防重復(fù)點擊。假設(shè)當(dāng)用戶在首頁2s內(nèi)頻繁點擊了商品或者誤觸了商品時,我們期望只打開一次商詳頁面,這時候就需要對點擊事件做節(jié)流。

首先我們需要定義一個注解,并聲明到點擊事件上,同時支持設(shè)置節(jié)流時長duration:

private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
        @Override
        @MethodThrottle(duration = 3000)
        public void onClick(View view) {
            
        }
}

接下來就是ASM的舞臺了?;仡櫸覀兩弦还?jié)中處理輸入源相關(guān)的代碼

Collection<DirectoryInput> directoryInputs = transformInput.getDirectoryInputs();
directoryInputs.forEach(new Consumer<DirectoryInput>() {
    @Override
    public void accept(DirectoryInput directoryInput) {
        try {
            // 處理輸入源
            handleDirectoryInput(directoryInput);
        } catch (IOException e) {
            System.out.println("handleDirectoryInput error:" + e.toString());
        }
    }
});

在第7行handleDirectoryInput()方法中,我們利用ASM修改Class文件:

/**
 * 處理文件目錄下的class文件
 */
private static void handleDirectoryInput(DirectoryInput directoryInput) throws IOException {
    List<File> files = new ArrayList<>();
    //列出目錄所有文件(包含子文件夾,子文件夾內(nèi)文件)
    listFiles(files, directoryInput.getFile());
    for (File file: files) {
        ClassReader classReader = new ClassReader(new FileInputStream(file.getAbsolutePath()));
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        MethodThrottleClassVisitor methodThrottleClassVisitor = new MethodThrottleClassVisitor(Opcodes.ASM5, classWriter);
        classReader.accept(methodThrottleClassVisitor, ClassReader.SKIP_FRAMES);

        byte[] code = classWriter.toByteArray();
        FileOutputStream fos = new FileOutputStream(file.getAbsolutePath());
        fos.write(code);
        fos.close();
    }
}

關(guān)鍵是MethodThrottleClassVisitor類,我們看它主要是怎么實現(xiàn)的:

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
    MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
    throttleMethodVisitor = new ThrottleMethodVisitor(mOwner, api, methodVisitor, access, name, descriptor);
    return throttleMethodVisitor;
}

visitMethod()方法返回一個自定義的MethodVisitor。ThrottleMethodVisitor在訪問每個方法時,若發(fā)現(xiàn)方法聲明了@MethodThrottle注解,就會插入節(jié)流代碼:

@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    System.out.println("ThrottleMethodVisitor visitAnnotation. clazzName:" + owner + ", methodName:" + methodName + ", access:" + access + ", descriptor" + descriptor + ", annotationDesc:" + desc);
    throttle = "Lcom/taobao/yyds/common/annotation/MethodThrottle;".equals(desc);
    ···
}
@Override
protected void onMethodEnter() {
    System.out.println("ThrottleMethodVisitor onMethodEnter. clazzName:" + owner + ", methodName:" + methodName + ", access:" + access + ", descriptor" + descriptor + ", throttle:" + throttle);
    if (throttle) {
        // 插入方法節(jié)流代碼
    }
}

具體的代碼就不放上去了,通過ASM ByteCode Viewer可以很方便地生成。這樣我們在平時代碼中要做方法節(jié)流時,只需要給方法聲明一個注解就可以了。

方法耗時日志

類似的,我們可以用這個鏈路實現(xiàn)很多AOP邏輯。給方法加耗時日志這一點,在上面的ASM基本使用那一節(jié)中其實已經(jīng)講過了。

運用到工程中來,其實就跟方法節(jié)流一樣,自定義一個注解,然后在Transform中加一點處理輸入流的邏輯就好了:

/**
 * 處理文件目錄下的class文件
 */
private static void handleDirectoryInput(DirectoryInput directoryInput) throws IOException {
    List<File> files = new ArrayList<>();
    //列出目錄所有文件(包含子文件夾,子文件夾內(nèi)文件)
    listFiles(files, directoryInput.getFile());
    for (File file: files) {
        // 方法節(jié)流
        methodThrottleASM(file);

        // 方法耗時
        methodTimeConsumeASM(file);
    }
}

如何調(diào)試

另外有一點我個人覺得還是挺重要的,那就是Gradle插件該如何調(diào)試。即使有ASM ByteCode Viewer插件的幫助,我們在插件中寫的ASM代碼也不可能一鍵完成,很大概率會碰到各種各樣的編譯錯誤問題。本地打日志又比較麻煩,所以調(diào)試手段是必不可少的。篇幅受限,這里就不額外寫了,可以參考Android Studio調(diào)試Gradle插件詳情

發(fā)布線上的額外工作

雖然我們已經(jīng)集成了ASM插件模塊,但并不意味著這樣就能上線了。至少還需要完成以下的工作才行。

插件項目的maven倉庫

因為在調(diào)試階段,插件的maven倉庫是用的本地的maven倉庫。但如果要集成進CI,肯定是需要線上的maven倉庫的,所以到時候需要申請上傳到某個maven倉中才行。

編譯影響評估

第一點是必須保證CI打包時沒問題。第二點就是看這樣做是否會影響到編譯時長,畢竟Transform是在編譯階段加了一個Task。如果對編譯時長有比較大的影響,還需要額外做一些編譯優(yōu)化的工作??梢杂?code>./gradlew --profile --rerun-tasks assembleDebug命令查看各環(huán)節(jié)的編譯耗時。

Tips

在做這個Demo的過程中,因為自己也是第一次接觸,遇到了不少坑,拿幾點貼一下:

  • 每次plugin改動都要重新發(fā)布一下,否則plugin中transform、asm的邏輯都不會更新,因為拿的是maven倉中的。
  • transform每次debug前都要clean一下,否則debug不進去。
  • 即使transform沒修改任何東西,也需要將源文件jar directory拷貝到目標(biāo)文件。否則最后編譯出的build/transforms文件夾中會少很多jar包,造成啟動時找不到各種Class而崩潰。
  • classpath引入后,造成啟動后某so崩潰。一直以為是插件寫的有什么問題,排查很久后也沒找到原因。最后拿崩潰棧去找so庫接口人,升級so版本后問題就解決了。

總結(jié)

通過本篇文章,我們了解到ASM的作用,學(xué)會ASM基本API的使用,進而利用AGP與Transform將ASM運用到實際項目中來。實戰(zhàn)中,ASM可以實現(xiàn)常見的AOP邏輯,如方法節(jié)流與方法耗時日志。不僅如此,當(dāng)我們以后為需要修改三方庫代碼而發(fā)愁時,或許可以想想,ASM能幫助我們搞定嗎?

到此這篇關(guān)于Android ASM插樁探索實戰(zhàn)詳情的文章就介紹到這了,更多相關(guān)Android ASM插樁探內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論