Android AOP三劍客之Javassist

前言

本章節(jié)更新的慢了些是复,最近公司多事之秋山涡,今天靜下心來(lái)把AOP最后入門(mén)篇補(bǔ)上幻碱,做事還要有頭和尾的狂秦。

Javassist

Javassist作用是在編譯器間修改class文件蚯涮,與之相似的ASM(熱修復(fù)框架女?huà)z)也有這個(gè)功能坯临,可以讓我們直接修改編譯后的class二進(jìn)制代碼,首先我們得知道什么時(shí)候編譯完成恋昼,并且我們要趕在class文件被轉(zhuǎn)化為dex文件之前去修改看靠。在Transfrom這個(gè)api出來(lái)之前,想要在項(xiàng)目被打包成dex之前對(duì)class進(jìn)行操作液肌,必須自定義一個(gè)Task挟炬,然后插入到predex或者dex之前,在自定義的Task中可以使用javassist或者asm對(duì)class進(jìn)行操作嗦哆。而Transform則更為方便谤祖,Transfrom會(huì)有他自己的執(zhí)行時(shí)機(jī),不需要我們插入到某個(gè)Task前面老速。Tranfrom一經(jīng)注冊(cè)便會(huì)自動(dòng)添加到Task執(zhí)行序列中粥喜,并且正好是項(xiàng)目被打包成dex之前。

傳送門(mén):android-aop-samples

定義JavassistPlugin橘券,固定寫(xiě)法沒(méi)啥說(shuō)的

public class JavassistPlugin implements Plugin<Project> {

void apply(Project project) {
    System.out.println("------------------開(kāi)始----------------------");
    System.out.println("這是我們的自定義插件!");
    //AppExtension就是build.gradle中android{...}這一塊
    def android = project.extensions.getByType(AppExtension)

    //注冊(cè)一個(gè)Transform
    def classTransform = new JavassistTransform(project);
    android.registerTransform(classTransform);

    System.out.println("------------------結(jié)束了嗎----------------------");
}
}

Transfrom

Gradle是通過(guò)一個(gè)一個(gè)Task執(zhí)行完成整個(gè)流程的额湘,其中肯定也有將所有class打包成dex的task。
(在gradle plugin 1.5 以上和以下版本有些不同)

1.5以下旁舰,preDex這個(gè)task會(huì)將依賴(lài)的module編譯后的class打包成jar锋华,然后dex這個(gè)task則會(huì)將所有class打包成dex
1.5以上,preDex和Dex這兩個(gè)task已經(jīng)消失箭窜,取而代之的是TransfromClassesWithDexForDebug

自定義Transfrom

public class JavassistTransform extends Transform {

private Project mProject;

public JavassistTransform(Project p) {
    this.mProject = p;
}

//transform的名稱(chēng)
//transformClassesWithMyClassTransformForDebug 運(yùn)行時(shí)的名字
//transformClassesWith + getName() + For + Debug或Release
@Override
public String getName() {
    return "JavassistTransform";
}

//需要處理的數(shù)據(jù)類(lèi)型毯焕,有兩種枚舉類(lèi)型
//CLASSES和RESOURCES,CLASSES代表處理的java的class文件磺樱,RESOURCES代表要處理java的資源
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
    return TransformManager.CONTENT_CLASS;
}

//    指Transform要操作內(nèi)容的范圍纳猫,官方文檔Scope有7種類(lèi)型:

//    EXTERNAL_LIBRARIES        只有外部庫(kù)
//    PROJECT                       只有項(xiàng)目?jī)?nèi)容
//    PROJECT_LOCAL_DEPS            只有項(xiàng)目的本地依賴(lài)(本地jar)
//    PROVIDED_ONLY                 只提供本地或遠(yuǎn)程依賴(lài)項(xiàng)
//    SUB_PROJECTS              只有子項(xiàng)目。
//    SUB_PROJECTS_LOCAL_DEPS   只有子項(xiàng)目的本地依賴(lài)項(xiàng)(本地jar)竹捉。
//    TESTED_CODE                   由當(dāng)前變量(包括依賴(lài)項(xiàng))測(cè)試的代碼
@Override
public Set<QualifiedContent.Scope> getScopes() {
    return TransformManager.SCOPE_FULL_PROJECT;
}

//指明當(dāng)前Transform是否支持增量編譯
@Override
public boolean isIncremental() {
    return false;
}

//    Transform中的核心方法芜辕,
//    inputs中是傳過(guò)來(lái)的輸入流,其中有兩種格式活孩,一種是jar包格式一種是目錄格式物遇。
//    outputProvider 獲取到輸出目錄,最后將修改的文件復(fù)制到輸出目錄憾儒,這一步必須做不然編譯會(huì)報(bào)錯(cuò)
@Override
public void transform(Context context,
                      Collection<TransformInput> inputs,
                      Collection<TransformInput> referencedInputs,
                      TransformOutputProvider outputProvider,
                      boolean isIncremental) throws IOException, TransformException, InterruptedException {
    System.out.println("你愁啥----------------進(jìn)入transform了--------------")
    //遍歷input
    inputs.each { TransformInput input ->
        //遍歷文件夾
        input.directoryInputs.each { DirectoryInput directoryInput ->
            //注入代碼
            MyInjects.inject(directoryInput.file.absolutePath, mProject)

            // 獲取output目錄
            def dest = outputProvider.getContentLocation(directoryInput.name,
                    directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)

            // 將input的目錄復(fù)制到output指定目錄
            FileUtils.copyDirectory(directoryInput.file, dest)
        }

        ////遍歷jar文件 對(duì)jar不操作询兴,但是要輸出到out路徑
        input.jarInputs.each { JarInput jarInput ->
            // 重命名輸出文件(同目錄copyFile會(huì)沖突)
            def jarName = jarInput.name
            println("jar = " + jarInput.file.getAbsolutePath())
            def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
            if (jarName.endsWith(".jar")) {
                jarName = jarName.substring(0, jarName.length() - 4)
            }
            def dest = outputProvider.getContentLocation(jarName + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
            FileUtils.copyFile(jarInput.file, dest)
        }
    }
    System.out.println("瞅你咋地--------------結(jié)束transform了----------------")
}

 }

主要說(shuō)下transform干嘛的 ?起趾?诗舰?在Transform里處理Task,通過(guò)inputs拿到一些東西训裆,處理完畢之后就輸出outputs眶根,而下一個(gè)Task的inputs則是上一個(gè)Task的outputs。

MyInjects.inject()插入代碼

public class MyInjects {
//初始化類(lèi)池
private final static ClassPool pool = ClassPool.getDefault();

public static void inject(String path, Project project) {
    //將當(dāng)前路徑加入類(lèi)池,不然找不到這個(gè)類(lèi)
    pool.appendClassPath(path);
    //project.android.bootClasspath 加入android.jar边琉,不然找不到android相關(guān)的所有類(lèi)
    pool.appendClassPath(project.android.bootClasspath[0].toString());
    //引入android.os.Bundle包属百,因?yàn)閛nCreate方法參數(shù)有Bundle
    pool.importPackage("android.os.Bundle");

    File dir = new File(path);
    if (dir.isDirectory()) {
        //遍歷文件夾
        dir.eachFileRecurse { File file ->
            String filePath = file.absolutePath
            println("filePath = " + filePath)
            if (file.getName().equals("MainActivity.class")) {

                //獲取MainActivity.class
                CtClass ctClass = pool.getCtClass("com.zxy.aop.MainActivity");
                println("ctClass = " + ctClass)
                //解凍
                if (ctClass.isFrozen())
                    ctClass.defrost()

                //獲取到OnCreate方法
                CtMethod ctMethod = ctClass.getDeclaredMethod("onCreate")

                println("方法名 = " + ctMethod)


                String insetBeforeStr = """ android.widget.Toast.makeText(this,"WTF emmmmmmm.....我是被插入的Toast代碼~!!",android.widget.Toast.LENGTH_LONG).show();
                                            """
                //在方法開(kāi)頭插入代碼

                ctMethod.insertBefore(insetBeforeStr);
                ctClass.writeFile(path)
                ctClass.detach()//釋放
            }
        }
    }

}

}

運(yùn)行效果圖

看下build/intermediates/transforms/HavassustTransform/MainActivity.class

總結(jié)

ClassPool、CtClass变姨、CtMethod核心類(lèi)的使用在這里展示的很詳細(xì)族扰。

1、初始化ClassPool設(shè)置
2定欧、通過(guò)包名取到對(duì)應(yīng)的CtClass
3渔呵、通過(guò)CtClass取到CtMethod對(duì)應(yīng)的“OnCreate”方法
4、CtMethodi插入代碼塊砍鸠,寫(xiě)文件扩氢,釋放,結(jié)束整個(gè)注入代碼過(guò)程爷辱。

最后

本章節(jié)只是介紹了自定義Transform以及Javassist的三大核心類(lèi)的使用录豺,通過(guò)本章節(jié)的學(xué)習(xí)可以了解apk的編譯過(guò)程,可以做很多好玩有意義的事情饭弓!

作為老司機(jī)巩检,這是彎道超車(chē)的必備秘籍,天下武功示启、唯快不破兢哭!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市夫嗓,隨后出現(xiàn)的幾起案子迟螺,更是在濱河造成了極大的恐慌,老刑警劉巖舍咖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矩父,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡排霉,警方通過(guò)查閱死者的電腦和手機(jī)窍株,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人球订,你說(shuō)我怎么就攤上這事后裸。” “怎么了冒滩?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵微驶,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我开睡,道長(zhǎng)因苹,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任篇恒,我火速辦了婚禮扶檐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胁艰。我一直安慰自己蘸秘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布蝗茁。 她就那樣靜靜地躺著醋虏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哮翘。 梳的紋絲不亂的頭發(fā)上颈嚼,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音饭寺,去河邊找鬼阻课。 笑死,一個(gè)胖子當(dāng)著我的面吹牛艰匙,可吹牛的內(nèi)容都是我干的限煞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼员凝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼署驻!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起健霹,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤旺上,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后糖埋,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體宣吱,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年瞳别,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了征候。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杭攻。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖疤坝,靈堂內(nèi)的尸體忽然破棺而出兆解,到底是詐尸還是另有隱情,我是刑警寧澤卒煞,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站叼架,受9級(jí)特大地震影響畔裕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜乖订,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一扮饶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乍构,春花似錦甜无、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至眠饮,卻和暖如春奥帘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仪召。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工寨蹋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扔茅。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓已旧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親召娜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子运褪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容