lancet 庫的工作原理

Lancet

lancet 是一個輕量級Android AOP框架。

dependencies{
    classpath 'me.ele:lancet-plugin:1.0.4'
}
apply plugin: 'me.ele.lancet'

dependencies {
    provided 'me.ele:lancet-base:1.0.4'
}

下面使用 Lancet 來 hook Cat 接口的 eat() 方法

  • Cat.java
package com.example.androidperfermance.aop.lancet;

public class Cat {
    public void eat() {
        System.out.println("貓吃老鼠");
    }

    @Override
    public String toString() {
        return "貓";
    }
}
  • 使用 Cat 方法
private void testLancet() {
    Cat cat = new Cat();
    cat.eat();//輸出 貓吃老鼠 
}

下面使用 Lancet 來 hook 這個 Cat 類的 eat() 方法

  • LancetHooker.java hook 類
package com.example.androidperfermance.aop.lancet;
import android.util.Log;
import java.lang.annotation.Target;
import me.ele.lancet.base.Origin;
import me.ele.lancet.base.Scope;
import me.ele.lancet.base.annotations.ImplementedInterface;
import me.ele.lancet.base.annotations.Insert;
import me.ele.lancet.base.annotations.Proxy;
import me.ele.lancet.base.annotations.TargetClass;

public class LancetHooker {

    @Insert(value = "eat", mayCreateSuper = true)
    @TargetClass(value = "com.example.androidperfermance.aop.lancet.Cat", scope = Scope.SELF)
    public void _eat() {
        //這裏可以使用 this 訪問當前 Cat 類的成員,僅用於Insert 方式的非靜態方法的Hook中.(暫時)
        System.out.println(">>>>>>>" + this);
        System.out.println(Log.getStackTraceString(new Throwable()));
        Origin.callVoid();  
    }
}

輸出結果

通過輸出結果可以看出,Lancet 是已經 hook 到對應的方法了。

分析 Lancet 對目標類做了什麼操作

程序中不需要顯示地去調用 LancetHooker 類,Lancet 內部是通過 Gradle Transform 來修改目標類的字節碼,**Transform 是指在項目在編譯成 .class 之後在生成 .dex 之前通過 ASM 去修改 .class 文件的。**下面這些類都是在打包編譯時生成的,因此上面引入 lancet 庫時只需要使用 privoded ,provided 表示在編譯時依賴,不需要在運行時依賴。

下面來分析 lancet 生成的類是如何工作的,Transfrom 生成的 class 文件放在 build/intermediates/transforms/lancet/debug/58/com/example/androidperfermance/aop/lancet/

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.example.androidperfermance.aop.lancet;

import android.util.Log;
import me.ele.lancet.base.Scope;
import me.ele.lancet.base.annotations.Insert;
import me.ele.lancet.base.annotations.TargetClass;

public class Cat {
    public Cat() {
    }

    public void eat() {
        Cat._lancet.com_example_androidperfermance_aop_lancet_LancetHooker__eat(this);
    }
    //eat 方法真正的邏輯
    private void eat$___twin___() {
        System.out.println("貓吃老鼠");
    }

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

    private static class _lancet {
        private _lancet() {
        }

        @Insert(
            value = "eat",
            mayCreateSuper = true
        )
        @TargetClass(
            value = "com.example.androidperfermance.aop.lancet.Cat",
            scope = Scope.SELF
        )
        static void com_example_androidperfermance_aop_lancet_LancetHooker__eat(Cat var0) {
            System.out.println(">>>>>>>" + var0);
            System.out.println(Log.getStackTraceString(new Throwable()));
            var0.eat$___twin___();
        }
    }
}
  • 對比我們自己 Cat 類,看 Lancet 生成 Cat.class 差異在哪裏?

針對目標類 Cat 生成一個內部類 Cat._lancet,當外部調用 Cat 對象 中的 eat()方法時內部會調用 _lancetcom_example_androidperfermance_aop_lancet_LancetHooker_eat 方法。

static void com_example_androidperfermance_aop_lancet_LancetHooker__eat(Cat var0) {
    System.out.println(">>>>>>>" + var0);
    System.out.println(Log.getStackTraceString(new Throwable()));
    var0.eat$___twin___();
 }

com_example_androidperfermance_aop_lancet_LancetHooker_eat 方法體內容就是在 LancetHooker 類中編寫的方法體,內部調用eat$___twin___(),它是 eat() 方法的真正實現。

private void eat$___twin___() {
    System.out.println("貓吃老鼠");
}

注意這個方法eat$___twin___()是私有的。 也就是說只能外界只能調用 eat() 方法,通過 eat() 再來調用 eat$___twin___()方法。

#總結

本文簡單地使用 Lancet 實現 hook 了 Cat 類的 eat() 方法,並根據這個 Lancet 生成的類,分析了 Lancet 是如何實現 hook 操作的。Lancet 這個庫還有很多使用方式,具體可以參考一下官方文檔 lancet

本文是筆者學習之後的總結,方便日後查看學習,有任何不對的地方請指正。

記錄於 2019年4月24號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章