一起用Gradle Transform API + ASM完成代碼織入呀~

前言

最近的工作內(nèi)容主要其實(shí)并不是說主攻插樁,但是這一次使用Lancet插樁給項(xiàng)目本來帶來了極大的收益已亥,這和工程的設(shè)計相關(guān),當(dāng)初的設(shè)計就是在對抖音中一個原有組件盡可能小的修改情況下静陈,完成我新功能的接入院溺,方案從SPI --> 主工程Lancet --> Lancet下沉到一個自定義組件中,一次次嘗試確實(shí)也是領(lǐng)會這個黑科技的恐怖之處了鹉勒。

先了解以下當(dāng)時的場景:

先比較一期和二期的優(yōu)勢和劣勢:實(shí)踐發(fā)現(xiàn)一期最后相較于二期的優(yōu)勢僅僅只有不影響主工程帆锋,而劣勢主要表現(xiàn)在三個方面:

  1. api改動時,impl組件需要聯(lián)動修改禽额。
  2. 當(dāng)時的環(huán)境決定锯厢,使用SPI方案時,會導(dǎo)致大量的本不需要過早獲取的數(shù)據(jù)被獲取了脯倒,導(dǎo)致運(yùn)行時工程性能降低实辑,另外還有反射在損耗性能。

但是二期方案也存在劣勢藻丢,我們也說了影響主工程剪撬,而且說Lancet的生效時機(jī)需要進(jìn)行把握,不可能讓他全局生效因?yàn)楸旧砭褪翘囟ㄇ闆r下悠反,全局時會影響編譯速度残黑,另外這在后期的維護(hù)上成本也有一定的增加。

以上的總結(jié)最后引出了方案三斋否,不影響主工程梨水,并且不需要把握生效時機(jī),只需要某組件給出Hook點(diǎn)如叼,就可以輕松完成工作冰木。

本文只探討怎么去實(shí)現(xiàn)AscpectJ這一類AOP方案的方法穷劈。

熱門的插樁方案探索

瀏覽了一下Github上比較熱門的插樁方案,看到普遍進(jìn)行使用的就是AspectJ還有Lancet社证,而作為AspectJ他的延伸中的拓展庫AspectJX宜肉,因?yàn)楸容^好的兼容性而受到廣泛使用。

AspectJX的使用方法

AspectJX是基于 gradle android插件1.5及以上版本設(shè)計使用的。

插件引入

// root -> build.gradle
dependencies {
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8'        
}
// app -> build.gradle
apply plugin: 'android-aspectjx'
復(fù)制代碼

如何使用

這里用的是一個他的權(quán)限請求庫Android_Permission_AspectjX,注意使用過程中發(fā)現(xiàn)一個Bug扁眯,給作為基類的Activity套上注解時并不會生效施敢,基類的方法是沒問題的。

// 1\. app --> build.gradle
compile 'com.firefly1126.permissionaspect:permissionaspect:1.0.1'
// 2\. 自定義Application
onCreate(){
    PermissionCheckSDK.init(Application);
}
// 3\. 使用注解的方式添加權(quán)限@NeedPermission
@NeedPermission(permissions = {Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public class BActivity extends Activity {}

//作用于類的方法
@NeedPermission(permissions = {Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
private void startBActivity(String name, long id) {
        startActivity(new Intent(MainActivity.this, BActivity.class));
    }
復(fù)制代碼

非常簡單的使用了兩個注解就已經(jīng)完成權(quán)限的申請愚屁。

這個庫的一些坑

這樣就已經(jīng)完成庫的導(dǎo)入了梦谜,但是查閱一些度娘的資料會發(fā)現(xiàn)這樣的問題發(fā)生庫的沖突。比如與支付寶sdk發(fā)生沖突捏肢,以下是一段用于復(fù)現(xiàn)代碼辩棒。

PayTask alipay = new PayTask(this);
復(fù)制代碼

這是由于AspectJX本身造成的窘俺,默認(rèn)會處理所有的二進(jìn)制代碼文件和庫对途,為了提升編譯效率及規(guī)避部分第三方庫出現(xiàn)的編譯兼容性問題,AspectJX提供include,exclude命令來過濾需要處理的文件及排除某些文件(包括class文件及jar文件)怀愧。當(dāng)然為了解決這樣的問題,開發(fā)者也提供了解決方案扛拨,也就是白名單耘分。

aspectjx {
    //排除所有package路徑中包含`android.support`的class文件及庫(jar文件)
    exclude 'android.support'
    // exclude '*'
    // 關(guān)閉AspectJX功能,默認(rèn)開啟
    enabled false
}
復(fù)制代碼

Lancet的使用

文章只做涉略绑警,更為具體的使用請查看倉庫:github.com/eleme/lance…

  1. 插件引入
// root --> build.gradle
dependencies {
    classpath 'com.android.tools.build:gradle:3.3.2'
    classpath 'me.ele:lancet-plugin:1.0.6'
}
// build.gralde
apply plugin: 'me.ele.lancet'
dependencies {
    compileOnly 'me.ele:lancet-base:1.0.6'
}
復(fù)制代碼
  1. Lancet的使用
public class LancetHooker {
    @Insert(value = "eat", mayCreateSuper = true)
    @TargetClass(value = "com.example.lancet.Cat", scope = Scope.SELF)
    public void _eat() {
        ((Cat)This.get()).bark();
        //這里可以使用 this 訪問當(dāng)前 Cat 類的成員求泰,僅用于Insert 方式的非靜態(tài)方法的Hook中.(暫時)
        System.out.println(">>>>>>>" + this);
        Origin.callVoid();
    }

    @Insert(value = "bark", mayCreateSuper = true)
    @TargetClass(value = "com.example.lancet.Cat", scope = Scope.SELF)
    public void _bark(){
        System.out.println("調(diào)用了bark");
        Origin.callVoid();
    }
}

復(fù)制代碼

當(dāng)定義了Hook點(diǎn),并且在編譯時被搜索到计盒,最后編譯完成之后的效果就會為如下所示渴频。

public class Cat {

    class _lancet {
        private _lancet() {
        }
        // 比如調(diào)用原本調(diào)用bark的方法,會重寫為調(diào)用com_example_lancet_LancetHooker__bark
        // 如果內(nèi)部存在Origin.Call()這一類的方法時北启,會對原本的方法在自己的調(diào)用點(diǎn)上進(jìn)行過程
        @Insert(mayCreateSuper = true, value = "bark")
        @TargetClass(scope = Scope.SELF, value = "com.example.lancet.Cat")
        static void com_example_lancet_LancetHooker__bark(Cat cat) {
            System.out.println("調(diào)用了bark");
            cat.bark$___twin___();
        }

        @Insert(mayCreateSuper = true, value = "eat")
        @TargetClass(scope = Scope.SELF, value = "com.example.lancet.Cat")
        static void com_example_lancet_LancetHooker__eat(Cat cat) {
            cat.bark();
            PrintStream printStream = System.out;
            printStream.println(">>>>>>>" + cat);
            cat.eat$___twin___();
        }
    }

    public void bark() {
        _lancet.com_example_lancet_LancetHooker__bark(this);
    }

    public void eat() {
        _lancet.com_example_lancet_LancetHooker__eat(this);
    }

    /* access modifiers changed from: private */
    public void eat$___twin___() {
        System.out.println("貓吃老鼠");
    }

    public String toString() {
        return "貓";
    }

    /* access modifiers changed from: private */
    public void bark$___twin___() {
        System.out.println("貓叫了叫");
    }
}
復(fù)制代碼

可以發(fā)現(xiàn)它的做法是對源代碼進(jìn)行修改卜朗,而修改的方式是建設(shè)一個靜態(tài)內(nèi)部類,和對應(yīng)的內(nèi)部方法咕村,通過重新設(shè)置調(diào)用鏈來進(jìn)行結(jié)果的完成场钉,那AspectJ呢,他是否是通過這樣的方式來進(jìn)行完成的呢懈涛?

AspectJ是如果實(shí)現(xiàn)的逛万?

權(quán)限的申請只通過幾個注解就能夠完成,那他是怎么做的呢批钠?我們可以通過jadx-gui來反編譯代碼進(jìn)行查看泣港。

因?yàn)锳spectJX默認(rèn)對所有文件生效,所以是否添加注解都會被劫持价匠,除非使用上文中的開白名單

public final class MainActivity extends BaseActivity {
    private static final /* synthetic */ JoinPoint.StaticPart ajc$tjp_0 = null;
    private HashMap _$_findViewCache;

    /* compiled from: MainActivity.kt */
    public class AjcClosure1 extends AroundClosure {
        public AjcClosure1(Object[] objArr) {
            super(objArr);
        }

        public Object run(Object[] objArr) {
            Object[] objArr2 = this.state;
            MainActivity.onCreate_aroundBody0((MainActivity) objArr2[0], (Bundle) objArr2[1], (JoinPoint) objArr2[2]);
            return null;
        }
    }

    static {
        ajc$preClinit();
    }

    private static /* synthetic */ void ajc$preClinit() {
        Factory factory = new Factory("MainActivity.kt", MainActivity.class);
        ajc$tjp_0 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, (Signature) factory.makeMethodSig("4", "onCreate", "com.example.stub.MainActivity", "android.os.Bundle", "savedInstanceState", "", "void"), 12);
    }

    public void _$_clearFindViewByIdCache() {
        HashMap hashMap = this._$_findViewCache;
        if (hashMap != null) {
            hashMap.clear();
        }
    }

    public View _$_findCachedViewById(int i) {
        if (this._$_findViewCache == null) {
            this._$_findViewCache = new HashMap();
        }
        View view = (View) this._$_findViewCache.get(Integer.valueOf(i));
        if (view != null) {
            return view;
        }
        View findViewById = findViewById(i);
        this._$_findViewCache.put(Integer.valueOf(i), findViewById);
        return findViewById;
    }

    static final /* synthetic */ void onCreate_aroundBody0(MainActivity ajc$this, Bundle savedInstanceState, JoinPoint joinPoint) {
        super.onCreate(savedInstanceState);
        ajc$this.setContentView((int) R.layout.activity_main);
    }

    /* access modifiers changed from: protected */
    public void onCreate(Bundle savedInstanceState) {
        JoinPoint makeJP = Factory.makeJP(ajc$tjp_0, (Object) this, (Object) this, (Object) savedInstanceState);
        PermissionAspect.aspectOf().adviceOnActivityCreate(new AjcClosure1(new Object[]{this, savedInstanceState, makeJP}).linkClosureAndJoinPoint(69648));
    }
}
復(fù)制代碼

通過編譯后的源碼查看可以發(fā)現(xiàn)当纱,你所寫的代碼已經(jīng)被通過一些特殊的方式來進(jìn)行了修改,所以我們就應(yīng)該有了自己的目標(biāo)了踩窖,注解 + 自動化代碼修改完成任務(wù)坡氯。

如何完成自動化代碼修改

這里我們首先需要借用的能力是Gradle Transform Api中的遍歷,而這個功能在你創(chuàng)建一個Android工程的時候Android Studio已經(jīng)自然而然給你集成了這一項(xiàng)能力洋腮。

這個Api的能力只有在Gradle Version 1.5+的時候才開放

那它的運(yùn)作方式是怎么樣的呢箫柳?小二,上圖啥供。

上述本是Apk完整的打包流程悯恍,但是如果使用了Transform Api將會多出我們紅框中的部分。當(dāng)然如果三方的.class Files的文件內(nèi)存在注解也是可能會被抓住的伙狐。所以這里我們知道了一個目標(biāo)是被編譯過后的.class文件們涮毫,而代碼的修改邏輯肯定是和我們的希望實(shí)現(xiàn)的邏輯有關(guān)的瞬欧。

看過了上面反編譯出來的一個代碼修改模式,我們可以先思考一下這種代碼修改可以如何去進(jìn)行罢防。比如說

public void fun(Login login){
    login.on();
}
復(fù)制代碼

但是我們想直接劫持這樣的方法艘虎,因?yàn)檫@個方法它只做了一個登陸操作,但是我想做身份驗(yàn)證呢咒吐?如果代碼中只有一處還好說野建,但是如果多處呢?可能我的代碼就變成了如下

public void fun(Login login){
    if(login.check()) login.on();
    else login.close()
}
復(fù)制代碼

上述代碼還是比較簡單的恬叹,但是有些時候這種邏輯的重復(fù)書寫是時常存在的候生,而且隨著代碼容量的增加而導(dǎo)致維護(hù)難度提高,如果有一天身份驗(yàn)證方法變了绽昼,那就涼透了唯鸭。這就是插樁經(jīng)常會被用到的地方 —— AOP面向切面,在代碼實(shí)現(xiàn)時绪励,你需要干的事情是給對應(yīng)的方法加上一個注解肿孵,處理邏輯統(tǒng)一完成。

插樁實(shí)現(xiàn)

第一個環(huán)節(jié):如何將插樁的能力植入

這里真的真的看了很多網(wǎng)上資料疏魏,質(zhì)量參差不齊停做,花了整整一天時間,終于把整個東西跑起來了?? ?? ?? 大莫,下面文章內(nèi)將給出我認(rèn)為最簡便的創(chuàng)建工程的方案蛉腌。

如果只是想要本地測試的話,這里給出的是最簡便的方案只厘,使用buildSrc(大小寫也要一致哦烙丛!)來作為Android Library的名字可以省去99%的麻煩。

最后會在文末給一個可以用于發(fā)版使用的實(shí)現(xiàn)方案介紹羔味。

那要先進(jìn)入第一步河咽,插件的使用。

為了能夠引入Gradle的能力赋元,請將倉庫內(nèi)的build.gradle的內(nèi)容修改成如下的形式忘蟹。

apply plugin: 'groovy'

dependencies {
    implementation gradleApi()//gradle sdk

    implementation 'com.android.tools.build:gradle:3.5.4'
    implementation 'com.android.tools.build:gradle-api:3.5.4'

    //ASM依賴
    implementation 'org.ow2.asm:asm:8.0'
    implementation 'org.ow2.asm:asm-util:8.0'
    implementation 'org.ow2.asm:asm-commons:8.0'
}

repositories {
    google()
    jcenter()
}
復(fù)制代碼

上述內(nèi)容完成sync以后,就需要生成一個插件能夠進(jìn)行使用搁凸。

/**
 * Create by yiyonghao on 2020-08-08
 * Email: yiyonghao@bytedance.com
 */
public class AsmPlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) {
        System.out.println("===========  doing  ============");
    }
}
復(fù)制代碼

并且在主工程的app --> build.gradle中添加語句apply plugin: com.example.buildsrc.AsmPlugin(包名.插件名)媚值。

很多工程說用Groovy來做,其實(shí)沒有必要护糖,直接Java就可以了褥芒。

如果到這一步,在build過程中能夠打印出=========== doing ============這個數(shù)據(jù)嫡良,說明插件已經(jīng)生效锰扶,那現(xiàn)在就要進(jìn)入下一步献酗,如何完成代碼的插樁了。

在不引入ASM之前少辣,整體Gradle Transform API為我們提供了什么樣的能力呢凌摄?先明確目標(biāo)涡驮,如果想要代碼的插樁狈惫,我們一定要進(jìn)行下面這樣的幾個步驟:

  1. 源碼文件獲瓤攴铩(可能是.class,也可能是.jar
  2. 文件修改

源碼文件獲取

為了獲取文件的路徑忙干,我們使用的能力就是Gradle Transform API所提供的Transform類,其中的transform()方法中的變量其實(shí)已經(jīng)自動為我們提供了很多他自身所具備的能力浪藻,就比如說文件遍歷捐迫。

public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation);
        //消費(fèi)型輸入,可以從中獲取jar包和class文件夾路徑爱葵。需要輸出給下一個任務(wù)
        Collection<TransformInput> inputs = transformInvocation.getInputs();
        //OutputProvider管理輸出路徑施戴,如果消費(fèi)型輸入為空,你會發(fā)現(xiàn)OutputProvider == null
        TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();

        for (TransformInput input : inputs) {
            for (JarInput jarInput : input.getJarInputs()) {
                File dest = outputProvider.getContentLocation(
                        jarInput.getFile().getAbsolutePath(),
                        jarInput.getContentTypes(),
                        jarInput.getScopes(),
                        Format.JAR);
            }
            for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
                File dest = outputProvider.getContentLocation(directoryInput.getName(),
                        directoryInput.getContentTypes(), directoryInput.getScopes(),
                        Format.DIRECTORY);
                transformDir(directoryInput.getFile(), dest); 
            }
        }
    }
復(fù)制代碼

通過如上的方式萌丈,就可以掃到我們的文件了赞哗,那就應(yīng)該要接入第二個步驟,如何進(jìn)行文件的修改辆雾?

文件修改

在上文中我從來沒有提及過Gradle Transform API關(guān)于修改代碼的邏輯肪笋,這是為什么呢?

還不是因?yàn)樗⒉惶峁┻@樣專項(xiàng)的功能度迂,所以這里就要引入我們經(jīng)常聽說的大將ASM來完成字節(jié)碼的修改了藤乙。這里開始將注意點(diǎn)放置到我們的兩個類AsmClassAdapterAsmMethodVisitor還有AsmTransform.weave()

關(guān)于ASM最最最最常涉及的是下面幾個核心類惭墓。

當(dāng)然我現(xiàn)在給出的Demo中有兩個類坛梁,AsmClassAdapter就是繼承了ClassVisitor用來訪問Class也就是我們的一個個類,而AsmMethodVisitor就是通過ClassVisitor的數(shù)據(jù)傳遞然后用于訪問類中存在的方法的腊凶。

private static void weave(String inputPath, String outputPath) {
        try {
            // 划咐。。吭狡。尖殃。。
            // 而文件結(jié)構(gòu)的訪問通過ASM基于的能力來進(jìn)行識別
            ClassReader cr = new ClassReader(is);
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
            AsmClassAdapter adapter = new AsmClassAdapter(cw);
            cr.accept(adapter, 0);
            // 划煮。送丰。。弛秋。器躏。
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
復(fù)制代碼

其實(shí)本質(zhì)上就是ASM對一個文件進(jìn)行分析操作以后俐载,讓我們只關(guān)注想要插入什么,以什么樣的方法去進(jìn)行插入登失,然后他會使用對應(yīng)的方案對字節(jié)碼進(jìn)行整改遏佣。

AsmClassAdapterAsmMethodVisitor的簡單實(shí)現(xiàn)
public class AsmClassAdapter extends ClassVisitor implements Opcodes {
    public AsmClassAdapter(ClassVisitor classVisitor) {
        super(ASM7, classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        return (mv == null) ? null : new AsmMethodVisitor(mv); // 1 -->
    }
}
復(fù)制代碼

MethodVisitor方法對于我們而言,就是對方法的一個插樁方案揽浙。

public class AsmMethodVisitor extends MethodVisitor{
    public AsmMethodVisitor(MethodVisitor methodVisitor) {
        super(ASM7, methodVisitor);
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
        //方法執(zhí)行之前打印
        mv.visitLdcInsn(" before method exec");
        mv.visitLdcInsn(" [ASM 測試] method in " + owner + " ,name=" + name);
        mv.visitMethodInsn(INVOKESTATIC,
                "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false);
        mv.visitInsn(POP);
        // 原有方法
        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

        //方法執(zhí)行之后打印
        mv.visitLdcInsn(" after method exec");
        mv.visitLdcInsn(" method in " + owner + " ,name=" + name);
        mv.visitMethodInsn(INVOKESTATIC,
                "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false);
        mv.visitInsn(POP);
    }
}
復(fù)制代碼

你可以實(shí)現(xiàn)更多類似這樣的方法状婶。而這樣做過之后,我們是否已經(jīng)完成了所謂了字節(jié)碼的修改了呢馅巷?

第二步:文件覆蓋

可能你跑不通膛虫,這里直接給出一個答案,并沒有完成5鲡稍刀!我們我們雖然會所把字節(jié)碼修改了,但是你是否有完成文件的覆蓋呢敞曹?

所以你能夠在Demo中發(fā)現(xiàn)存在這樣的代碼账月,比如:

  1. weave()方法
private static void weave(String inputPath, String outputPath) {
        try {
            // 存在新文件的創(chuàng)建
            FileInputStream is = new FileInputStream(inputPath);
            ClassReader cr = new ClassReader(is);
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
            AsmClassAdapter adapter = new AsmClassAdapter(cw);
            cr.accept(adapter, 0);
            FileOutputStream fos = new FileOutputStream(outputPath);
            fos.write(cw.toByteArray());
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
復(fù)制代碼
  1. FileUtils.copyFile(jarInput.getFile(), dest);存在jar包的位置遷移,這都是為了將新的代碼進(jìn)行存儲

完成到這里澳迫,我們在去看一下最后生成的代碼到底是什么樣的局齿。(文件路徑:app --> build --> intermediates --> transform --> 包名 --> debug --> 一直到你的文件)比如說我本地生成的MainActivity.java

public class MainActivity extends AppCompatActivity {
    public MainActivity() {
        Log.i(" before method exec", " [ASM 測試] method in androidx/appcompat/app/AppCompatActivity ,name=<init>");
        super();
        Log.i(" after method exec", " method in androidx/appcompat/app/AppCompatActivity ,name=<init>");
    }

    protected void onCreate(Bundle savedInstanceState) {
        Log.i(" before method exec", " [ASM 測試] method in androidx/appcompat/app/AppCompatActivity ,name=onCreate");
        super.onCreate(savedInstanceState);
        Log.i(" after method exec", " method in androidx/appcompat/app/AppCompatActivity ,name=onCreate");
        Log.i(" before method exec", " [ASM 測試] method in com/example/asm/MainActivity ,name=setContentView");
        this.setContentView(2131361820);
        Log.i(" after method exec", " method in com/example/asm/MainActivity ,name=setContentView");
        Log.i(" before method exec", " [ASM 測試] method in android/util/Log ,name=e");
        Log.e("aa", "aa");
        Log.i(" after method exec", " method in android/util/Log ,name=e");
    }
}
復(fù)制代碼

如果說你覺得好麻煩啊纲刀,那你也可以使用一個插件ASM Bytecode Outline的工具來完成插樁后代碼的查看

每一個方法最后都被我們插入了我們要插入的代碼项炼,那ok,說明離我們通過注解來進(jìn)行插樁的目標(biāo)已經(jīng)邁出了一大步示绊。

如何通過注解完成

既然要用注解來完成事件锭部,那這個時候我們就創(chuàng)建一個注解,但是請注意其中的@Retention注解寫法面褐,是需要在編譯期的時候進(jìn)行生效的拌禾。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ASM {}
復(fù)制代碼

然后你可以在MainActivity.java中加入方法,并加上這個注解展哭。那接下來的事情是什么呢湃窍?想必就是掃到這個注解了,也就是使用了visitAnnotation()的方法匪傍。

@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
        return super.visitAnnotation(descriptor, visible);
    }
復(fù)制代碼

但是縱觀繼承過來的方法您市,很顯然并不能說它本身并不能去修改這個注解所對應(yīng)的方法,所以我們最后的妥協(xié)只能是通過加入標(biāo)示符號役衡,當(dāng)要進(jìn)行方法插入的時候告訴visitMethodInsn()我這段代碼他是需要去進(jìn)行插入的茵休。

@Override
    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
        if(ANNOTATION_TRACK_METHOD.equals(descriptor)) isMatch = true;
        return super.visitAnnotation(descriptor, visible);
    }
復(fù)制代碼

visitMethodInsn()這個方法在插入之前需要先進(jìn)行判定,如此需要才進(jìn)行插樁。以下就是插樁之后的結(jié)果:

public class MainActivity extends AppCompatActivity {
    public MainActivity() {
    }

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(2131361820);
        Log.e("aa", "aa");
    }

    @Cat
    public void fun() {
        Log.d("tag", "onCreate start");
        Log.d("tag", "onCreate end");
    }

    @ASM
    public void fun1() {
    }
}
復(fù)制代碼

發(fā)布一個可以給別人用的插件

這個時候你不要在去在意Module的名字了榕莺,定義你想要的名字俐芯。為了方便起見,可以選擇先拷貝一份之前buildSrc中寫好的代碼钉鸯。既然是要發(fā)布吧史,那我們首先要干的事情就是使用Gradle進(jìn)行upload操作了。

// 在你新設(shè)置的Module --> build.gradle中加入以下代碼唠雕,你可以diy
uploadArchives {
    repositories.mavenDeployer {
        repository(url: uri('../repo'))
        pom.groupId = 'com.example.asm'
        pom.artifactId = 'asm_plugin'
        pom.version = '1.0.0'
    }
}
復(fù)制代碼

但是這個時候發(fā)布了并且在主工程進(jìn)行引入的話贸营,其實(shí)還是找不到我們的Plugin插件的。

因?yàn)樗€需要一步操作及塘,創(chuàng)建如下的目錄莽使,這是為了讓我們發(fā)布的文件能夠被發(fā)現(xiàn)

implementation-class = com.example.asm_plugin.AsmPlugin // 插件在包中位置給出
復(fù)制代碼

最后在root --> build.gralde中引入repo锐极,就可以像buildSrc一樣生效了笙僚。

buildscript {
    repositories {
        google()
        jcenter()
        maven {
            url uri("repo")
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.4'
        classpath 'com.example.asm:asm_plugin:1.0.0'
    }
}
復(fù)制代碼

覺得有用的話,點(diǎn)個贊吧~~也歡迎在下方評論灵再,提出你的見解肋层。

作者:ClericYi
鏈接:https://juejin.im/post/6863276629029126158

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市翎迁,隨后出現(xiàn)的幾起案子栋猖,更是在濱河造成了極大的恐慌,老刑警劉巖汪榔,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒲拉,死亡現(xiàn)場離奇詭異,居然都是意外死亡痴腌,警方通過查閱死者的電腦和手機(jī)雌团,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來士聪,“玉大人锦援,你說我怎么就攤上這事×樗拢” “怎么了区岗?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵叮称,是天一觀的道長颅拦。 經(jīng)常有香客問我距帅,道長碌秸,這世上最難降的妖魔是什么讥电? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任恩敌,我火速辦了婚禮月趟,結(jié)果婚禮上孝宗,老公的妹妹穿的比我還像新娘因妇。我一直安慰自己婚被,他們只是感情好摔寨,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淑廊,像睡著了一般季惩。 火紅的嫁衣襯著肌膚如雪画拾。 梳的紋絲不亂的頭發(fā)上青抛,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天适室,我揣著相機(jī)與錄音捣辆,去河邊找鬼汽畴。 笑死整袁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芋忿。 我是一名探鬼主播戈钢,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了隔箍?” 一聲冷哼從身側(cè)響起脚乡,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤俯艰,失蹤者是張志新(化名)和其女友劉穎竹握,沒想到半個月后污秆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體良拼,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了际乘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖关拒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涌韩,我是刑警寧澤靶擦,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布踩蔚,位于F島的核電站馅闽,受9級特大地震影響福也,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一嗦篱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辽装,卻和暖如春拾积,著一層夾襖步出監(jiān)牢的瞬間一死,已是汗流浹背摘符。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓肚邢,卻偏偏與公主長得像贱纠,于是被迫代替她去往敵國和親浦夷。 傳聞我的和親對象是個殘疾皇子剃执,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350