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()方法時內部會調用 _lancet
的 com_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號