###一、原理介紹
1、App構(gòu)建是將代碼編譯為.class文件,然后打包成dex文件之后輸出apk
2、Gradle構(gòu)建App由一個個Task組成,每個Task作用實際上是接收一個輸入(編譯App所需的資源)然后進行處理然后有一個輸出
3、Gradle1.5以后提供了transform-api可以在代碼轉(zhuǎn)化為.class文件之后再打包成dex文件之前對它進行處理
4、Javassist可以處理.class文件
所以我們可以通過自定義gradle插件的方式利用javassist在打包的過程中修改.class文件,這樣編譯出來的apk文件中就會是我們修改過的class
###二、Transfrom-API介紹
getName:用于指明本Transform的名字,這個 name 并不是最終的名字,在TransformManager 中會對名字再處理
getInputTypes:用于指明Transform的輸入類型,可以作為輸入過濾的手段
????–CLASSES表示要處理編譯后的字節(jié)碼,可能是 jar 包也可能是目錄
?????–RESOURCES表示處理標準的 java 資源
getScopes:用于指明Transform的作用域
????–PROJECT? ?????????????????????? 只處理當前項目
????–SUB_PROJECTS? 只處理子項目
????–PROJECT_LOCAL_DEPS? 只處理當前項目的本地依賴,例如jar, aar
????–EXTERNAL_LIBRARIES? 只處理外部的依賴庫
????–PROVIDED_ONLY? 只處理本地或遠程以provided形式引入的依賴庫
????–TESTED_CODE? ?????????????????????? 只處理測試代碼
isIncremental:用于指明是否是增量構(gòu)建。
transform:核心方法,用于自定義處理,在這個方法中我們可以拿到要處理的.class文件路徑、jar包路徑、輸出文件路徑等,拿到文件之后就可以對他們進行操作
利用Transform-api處理.class文件有個標準流程,拿到輸入路徑->取出要處理的文件->處理文件->移動文件到輸出路徑
上圖展示的代碼中沒有包含處理過程,我們只需要在FileUtils.copy函數(shù)之前對拿到的文件進行處理即可
###三、javassist介紹
? ??介紹:Javassist是一個動態(tài)類庫,可以用來檢查、”動態(tài)”修改以及創(chuàng)建 Java類。其功能與jdk自帶的反射功能類似,但比反射功能更強大。
? ? 常用類:
? ??ClassPool:javassist的類池,使用ClassPool類可以跟蹤和控制所操作的類,它的工作方式與 JVM類裝載器非常相似,
????CtClass: CtClass提供了檢查類數(shù)據(jù)(如字段和方法)以及在類中添加新字段、方法和構(gòu)造函數(shù)、以及改變類、父類和接口的方法。不
? ??????過,????Javassist 并未提供刪除類中字段、方法或者構(gòu)造函數(shù)的任何方法。
????CtField:用來訪問域
????CtMethod :用來訪問方法?
? ?CtConstructor:用來訪問構(gòu)造器
? ??基本用法
? ? ? ? ?1、添加類搜索路徑
????????? ClassPool pool =ClassPool.getDefault();
????????? ?pool.insertClassPath("/usr/local/javalib");
????????2、添加方法
? ? ? ? ?CtClass point =ClassPool.getDefault().get("Point");
? ? ? ? ?CtMethod m =CtNewMethod.make( "public int xmove(int dx) { x += dx; }", point);point.addMethod(m);
????????3、修改方法
? ? ? ? ?CtClass point =ClassPool.getDefault().get("Point");?
? ? ? ? ?CtMethod m= point.getDeclaredMethod(“show", null)
? ? ? ? ?m.insertAfter(“System.out.prinln(“x:” + x + “,y:) + y”))
????????4、添加字段
?????? ? CtClass point =ClassPool.getDefault().get("Point");
????????? CtField f = newCtField(CtClass.intType, "z", point);
? ????????point.addField(f);
###四、自定義Gradle插件
? ? 自定義插件可以直接新建一個名為buildSrc的module不過這樣的插件只能自己工程用,還有另外一種方式可以發(fā)布到j(luò)center提供別別人用也可以發(fā)布到本地自己使用。這里是第二種方式的步驟:這里是原文
? ??1、新建一個 Module,此Module 用于開發(fā)插件,類型選什么都無所謂,后面會大改。
????2、在 Project 目錄視圖模式下,清空build.gradle 文件的內(nèi)容,刪除其余的所有文件。
????3、然后在 module 中新建多個文件夾?src/main/groovy?,再新建包名文件夾。 在 main 目錄下再新建resources?目錄,在resources?目錄下再新
? ??建?META-INF?文件夾,再新建文件夾gradle-plugins,這樣就完成了 gradle插件的目錄結(jié)構(gòu)搭建
????4、打開?build.gralde?文件,替換全部內(nèi)容
????5、編寫插件內(nèi)容。在剛剛新建的包名下 再次新建一個文件?MyPlugin.groovy?,注意文件類型,一定是?groovy?類型文件
????6、在resources/META-INF/gradle-plugins?目錄下新建一個?.properties?文件,注意該文件的命名就是你使用此插件時的名稱,這里命名
? ??為?com.app.myplugin.properties?,一定要注意后綴名稱,那么使用時的名稱就是com.app.myplugin,文件里面的內(nèi)容填寫如下: ????implementation-class=com.app.plugin.MyPlugin,這里是?key = value?的形式,值就是剛剛自定義的 插件( groovy 文件 )的全名,也就是徑加上類名稱。
????7、發(fā)布插件到本地,修改build.gradle文件,這個時候右側(cè)的 gradle Toolbar 就會在module下多出一個task :upload-uploadArchives。?clean工程然后運行upload-uploadArchives
????8、引用插件
? ? ? a、修改工程根目錄下的build.gradle
? ? ? ?b、在module的目錄下的build.gradle中添加
????? apply plugin:'com.app.plugin.myplugin' //這里就填寫.properties 文件的名稱
###五、實操
? ? 我們可以按照上面的步驟進行一次demo編寫,這里要做的功能是在代碼中所有的View.OnClickListener類的onClick方法中插入一個Toast,在每次編譯之后我們可以到文件夾D:\as_workspace\sample\javassist\app\build\intermediates\transforms\ModifyTransform\debug下查看.class文件是否修改成功,D:\as_workspace\sample\javassist\這個是代碼工程目錄ModifyTransform是自定義Gradle插件的名稱。修改class文件在實際中還是挺有用處的,比如我們可以用來做無埋點或者修改第三方j(luò)ar包中的bug。