混淆的另一重境界
前言
今天給大家講解了一個Gradle插件的實現方法和原理,對于想深入了解Android打包編譯,gradle插件實現的開發者來說,絕對是一篇不錯的案例。
Mess介紹
眾所周知,我們開混淆打包后生成的apk里,Activity、自定義View、Service等出現在xml里的相關Java類默認都會被keep住,那么這對于app的保護是不足夠好的,Mess就是來解決這個問題,把即使出現在xml文件中的Java類照樣混淆。
使用
此外,Mess還提供一個可選配置, ignoreProguard ,由于有些依賴庫本身也配置了相關混淆配置,如 com.android.support:recyclerview-v7 、 com.jakewharton:butterknife 等,那么這些文件都將會被添加到 proguardFiles 中,導致依賴庫無法被混淆,所以 ignoreProguard 配置就是來解決這個問題的。
比如忽視 com.android.support:recyclerview-v7 的混淆配置文件,則直接

實現原理
先來看看Android gradle plugin在構建時最后所走的幾個task:

其中有幾個關鍵性的task,可以看到 :app:transformClassesAndResourcesWithProguardForRelease 是走在 :app:packageRelease 之前的,那么我們就在打包前對混淆的task做些操作來實現我們的目的。
-
hook transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}
-
hook ProcessAndroidResources Task,將生成的 aapt_rules.txt 中內容清空
-
如果需要混淆依賴庫,則刪除依賴庫中的 proguard.txt 文件
-
遍歷一遍mapping.txt獲取所有Java類名的的映射關系得到一個Map
-
拿映射Map替換AndroidManifest.xml里的Java原類名
-
拿映射Map替換layout、menu和value文件夾下的xml的Java原類名
-
重新跑 ProcessAndroidResources Task
-
恢復之前刪除依賴庫中的 proguard.txt 文件
以上就是Mess干的關鍵性的東西,接下來依次說明。
hooktransformClassesAndResourcesWithProguardFor${variant.name}
這個task是處理類和資源混淆的,也是我們的突破口,Mess中大部分自定義task都是圍繞在這個task執行的,之后會有詳解。
hookProcessAndroidResourcesTask,將生成的aapt_rules.txt中內容清空
這一步是雖說只是把 aapt_rules.txt 文件中的內容清空,但是確實Mess Plugin能成功的最關鍵的一步。
ProcessAndroidResourcestask會生成一個 aapt_rules.txt ,可見源碼 ProcessAndroidResources.groovy ,aapt_rules.txt里會keep住我們在xml里所書寫的那些Activity、自定義View等Java類名部分,還可以看到 JackTask.java 里的相關代碼:

其中getProcessAndroidResourcesProguardOutputFile方法所對應的文件就是我們所需要清空的aapt_rules.txt,可以在 VariantScope.java 中查看。

很明顯,aapt_rules.txt所keep住的所有內容都將會添加到最后的混淆配置中,因此,我們需要在 ProcessAndroidResources 這個Task執行之后清空aapt_rules.txt中的內容,以保證編譯出的main.jar中的所有.class都是混淆后的。
相關代碼如下:

如果需要混淆依賴庫,則刪除依賴庫中的proguard.txt文件
這一步就是刪除依賴庫中所保護的內容,具體 proguard.txt 文件位于 app目錄下/build/intermediates/exploded-aar/依賴庫maven名/proguard.txt 。
Mess中直接將 proguard.txt 文件名最后加上 ~ ,如 proguard.txt~ ,在linux中表示備份,以便之后文件的恢復。
相關代碼如下:
遍歷一遍mapping.txt獲取所有Java類名的的映射關系得到一個Map
之前第一步已經將生成的main.jar中所有的.class文件做相關混淆了,那么我們之前所在xml里寫的還是原來的Java類名,因此,我們想要替換xml里的Java類名,就得先知道原先的類名被替換成什么了,這個時候就得依賴mapping.txt了。
直接遍歷:

這樣后map里就存有所有類名的映射關系了,但是有個小問題要注意,假如存在這種情況,me.ele.foo -> me.ele.a,me.ele.fooNew -> me.ele.b,也就是恰巧有類名是另一個類名的開始部分,那么這樣對我們之后的替換是會有bug的,會導致fooNew被替換成了aNew。因此,拿到map后需要對map做一次原類名長度的降序排序(也就是map中的key),以避免這個bug發生。相關代碼如下:

至此,一個正確的map已經拿到,接下來就是靠這個map來對相關的xml文件做替換了。
拿映射Map替換AndroidManifest.xml里的Java原類名
細心活,拿到AndroidManifest.xml一行一行讀取,匹配到相關字符串則進行替換,但這里有個小坑,由于Java內部類的類名是用 $ 符號分割的,剛好它又是正則表達式表示匹配字符串的結尾,因此對于內部類,我們應該現將 $ 符號先替換成其他字符串,然后再做類名的替換,Mess中是替換成 inner ,相關代碼如下:

拿映射Map替換layout、menu和value文件夾下的xml的Java原類名
前一步已經把AndroidManifest.xml中的對應Java類名替換了,這一步就是替換layout、menu和value這三個文件夾下的xml內容,感謝groovy語法讓整件事情變得非常簡單。layout、menu文件夾大家能立馬理解,那么value呢?其實就是behavior引入后才存在的,所以value文件夾千萬別忽視。
相關代碼如下:

至此,整個工程的main.jar中的.class文件以及資源文件都替換成相互匹配的混淆后的名稱了。
重新跑ProcessAndroidResourcesTask
前些步驟hook后 ProcessAndroidResources Task之后我們已經把靜態的文件都替換好了,那么接下來就還得依靠Android gradle plugin的原有tasks了,于是乎我們重新執行 ProcessAndroidResources Task。

恢復之前刪除依賴庫中的proguard.txt文件
有頭有尾。
尾語
想要寫出Mess這樣的plugin,對Android整個打包流程是要相當熟悉的,這樣才能知道什么時候該hook什么task,平常開發過程中盡量不要直接點擊run按鈕,應該直接通過gradle assemble** 構建,這樣無數次的看構建過程中經歷哪些task,然后去閱讀相關task源碼,這樣對整個打包流程才會越來越胸有成竹。
Mess有個小遺憾,那就是ButterKnife這個庫在絕大多數app中都使用了,但是ButterKnife的混淆規則中有對使用注解的方法名和變量名做保護,這樣就比較尷尬了,會導致Mess對使用ButterKnife庫的app而言是沒多大作用的。
但是不要灰心,ButterMess這個Lib就來解決這個問題,接下來會寫篇詳解ButterMess的文章,先放個ButterMess的鏈接: https://github.com/peacepassion/ButterMess
喜歡就點擊原文鏈接,傳送Github
