咱們都知道JAVA是面向對象(繼承、封裝、多態),而插樁的意義在於面向切面(AOP),可想而知單方面的面向對象開發有許多的侷限性,而結合面向切面編程能夠說補足了咱們的這種侷限性。舉個例子:在onClick中通常都要作防抖動操做,這樣是爲了不屢次打開頁面的問題。通常實現的話是在每一個onClick實現第二次點擊的時候加個時間判斷。而插樁的話業務端能夠不寫任何代碼經過插樁的方法把這個時間判斷插入的字節碼裏面。java
從標題名字看android
銀行系統會有一個取款流程,咱們能夠把方框裏的流程合爲一個,另外系統還會有一個查詢餘額流程,咱們先把這兩個流程放到一塊兒,有沒有發現,這個二者有一個相同的驗證流程,咱們先把它們圈起來再說下一步,有沒有想過能夠把這個驗證用戶的代碼是提取出來,不放到主流程裏去呢,這就是AOP的做用了,有了AOP,你寫代碼時不要把這個驗證用戶步驟寫進去,即徹底不考慮驗證用戶。編程
這是app打包流程的整個過程而我把這個打包流程主要分爲一下步驟:api
字節碼插樁入口:咱們知道Android程序從Java源代碼到可執行的Apk包主要分析兩個環節:數組
咱們要想對字節碼進行修改,只須要在javac以後,dex以前對class文件進行字節碼掃描,並按照必定規則進行過濾及修改就能夠了,這樣修改事後的字節碼就會在後續的dex打包環節被打到apk中,這就是咱們的插樁入口。
bash
每一個Transform其實都是一個gradle task,Android編譯器中的TaskManager將每一個Transform串連起來,第一個Transform接收來自javac編譯的結果,以及已經拉取到在本地的第三方依賴(jar. aar),還有resource資源,注意,這裏的resource並不是android項目中的res資源,而是asset目錄下的資源。這些編譯的中間產物,在Transform組成的鏈條上流動,每一個Transform節點能夠對class進行處理再傳遞給下一個Transform。咱們常見的混淆,Desugar等邏輯,它們的實現現在都是封裝在一個個Transform中,而咱們自定義的Transform,會插入到這個Transform鏈條的最前面。
app
對於Android Gradle Plugin 版本在1.5.0及以上的狀況,Google官方提供了transformapi用做字節碼插樁的入口。框架
implementation 'com.android.tools.build:gradle:1.5.0'複製代碼
通常使用方法爲:extends Transform重寫transform()maven
須要引入Instrumentation性能
經過Java Instrumentation機制,爲得到插樁入口,對於apk build過程進行了兩處插樁(即hook),圖中標紅部分:
Instrumentation:指的是能夠用獨立於應用程序以外的代理(agent)程序來監測和協助運行在JVM上的應用程序。這種監測和協助包括但不限於獲取JVM運行時狀態,替換和修改類定義等。
在build進程,對ProcessBuilder.start()方法進行插樁
ProcessBuilder類是J2SE 1.5在java.lang中新添加的一個新類,此類用於建立操做系統進程,它提供一種啓動和管理進程的方法,start方法就是開始建立一個進程,對它進行插樁,使得經過下面方式啓動dx.jar進程執行dex任務時:
java dex.jar com.android.dx.command.Main --dex …........複製代碼
增長參數-javaagent agent.jar,使得dex進程也可使用Java Instrumentation機制進行字節碼插樁
在dex進程
對咱們的目標方法com.android.dx.command.Main.processClasses進行字節碼插入,從而實現打入apk的每個項目中的類都按照咱們制定的規則進行過濾及字節碼修改。
build進程使用Instrumentation的方式時以前敘述過的VirtualMachine.loadAgent方式(方式二),dex進程中的方式則是-javaagent agent.jar方式(方式一)。
由此,咱們得到了進行字節碼插樁的入口,下面咱們就使用ASM庫的API,對項目中的每個類進行掃描,過濾,及字節碼修改。
一、建立一個Android library Module工程
二、build.gradle改爲groovy方式
apply plugin: 'groovy'
dependencies {
compile gradleApi()
compile localGroovy()
}複製代碼
三、新建.groovy類繼承 Plugin並實現apply方法,注意:類的後綴再也不是.java而是.groovy
四、在main下建立resources目錄
五、增長對應的maven deployer發佈到本地或遠程倉庫
六、使用已發佈的倉庫
Framework | First time | Later times |
---|---|---|
Javassist | 257 | 5.2 |
BCEL | 473 | 5.5 |
ASM | 62.4 | 1.1 |
可使用一個插件[ASM Bytecode Outline]更有效的用ASM編寫字節碼
ASM(core api) 按照visitor模式按照class文件結構依次訪問class文件的每一部分,有以下幾個重要的visitor。
這個類會將 .class 文件讀入到 ClassReader 中的字節數組中,它的 accept 方法接受一個 ClassVisitor 實現類,並按照順序調用 ClassVisitor 中的方法
ClassWriter 是一個 ClassVisitor 的子類,是和 ClassReader 對應的類,ClassReader 是將 .class 文件讀入到一個字節數組中,ClassWriter 是將修改後的類的字節碼內容以字節數組的形式輸出。
MethodVisitor 是一個抽象類,當 ASM 的 ClassReader 讀取到 Method 時就轉入 MethodVisitor 接口處理。
AdviceAdapter 是 MethodVisitor 的子類,使用 AdviceAdapter 能夠更方便的修改方法的字節碼。
其中比較重要的幾個方法以下:
類android.widget.AdapterView.OnItemClickListener的全限定名爲:
android/widget/AdapterView$OnItemClickListener複製代碼
在class文件中類型 boolean用「Z」描述,數組用「[」描述(多維數組可疊加),那麼咱們最多見的自定義引用類型呢?「L全限定名;」.例如:
Android中的android.view.View類,描述符爲「Landroid/view/View;」
2.方法描述符的組織結構爲:
(參數類型描述符)返回值描述符複製代碼複製代碼
其中無返回值void用「V」代替,舉例:
方法boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) 的描述符以下:
(Landroid/widget/ExpandableListView;Landroid/view/View;IJ)Z複製代碼
對上圖中三個步驟的詳細說明:
ASM的ClassVisitor對全部類的class文件進行掃描,在visitMethod()方法中判斷是否是BaseActivity,若是是進行步驟二,不然終止掃描;
ClassVisitor每掃描到一個方法時,在visitMethod中進行以下斷定:
若是斷定經過,則證實本次掃描到的方法是須要注入字節碼的方法,而後將
將掃描邏輯交給MethodVisitor,進行字節碼的修改(步驟三)。
假設待修改的方法以下:
public int test() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}複製代碼
修改以後須要變成:
public int test() {
long startTime = System.currentTimeMillis();
try {
Thread.sleep(1000);}
catch (InterruptedException e){
e.printStackTrace();
}
long timing = System.currentTimeMillis() - startTime;
BlockManager.timingPage(getLocalClassName(), timing);
}複製代碼