標籤:
一、關於前面四篇博文
Android熱補丁動態修複技術(一):從Dex分包原理到熱補丁
Android熱補丁動態修複技術(二):實戰!CLASS_ISPREVERIFIED問題!
Android熱補丁動態修複技術(三)—— 使用Javassist注入位元組碼,完成熱補丁架構雛形(可使用)
Android熱補丁動態修複技術(四):自動化產生補丁——解決混淆問題
前兩篇博文主要是介紹熱補丁修複技術的一些原理和實現方案。
而後面兩篇博文主要是介紹如何使用代碼實現整個熱補丁架構,但是架構寫的真的很糟糕,很多多餘的操作。而這很大一部分原因是使用了transform,在混淆的時候transform並不好用。
以下是我在github上重構好的熱補丁架構,求star (??`ω′?)
https://github.com/AItsuki/HotFix
1. 支援混淆
2. 自動產生帶簽名的補丁包
3. 載入補丁包時會進行簽名校正
圖中的patch檔案夾就是自動產生的補丁包儲存目錄了,裡面有打成jar包之前的class,如果patch.jar打包失敗,還能繼續手動打包。
更詳細的介紹和使用方式請移步到github,再說一次:求star (??`ω′?)
二、架構的實現思路
在第四篇博文中,我們發現在混淆的情況下,transform使用起來真的很反人類,因為transform只能在混淆之前對class進行操作,無法將transform添加到混淆之後。
所以以下思路,我放棄了使用transform,而是直接在dextransform這個任務的dofirst中進行操作。
在重構項目之前,我先記錄下了這些思路和流程,然後根據這個流程來實現熱補丁架構,效率真的快了很多。
2.1 定義熱補丁架構的使用方式
release簽名打包作為發布版本,每次release打包都會重建hash.txt和mapping.txt(開啟混淆的情況下才有mapping)
每次debug啟動並執行時候(直接運行項目或者buildapk),都會通過校正hash.txt和mapping.txt產生已簽名補丁包。
直接將補丁包放到sdcard中即可完成熱修複
載入補丁的時候需要進行簽名校正,防止惡意代碼注入
2.2 代碼流程
拋棄transform,使用純hook的方式實現。
主要hook的task有這幾個:
- transformClassesWithDexForRelease
- transformClassesWithDexForDebug
- transformClassesAndResourcesWithProguardForRelease
- transformClassesAndResourcesWithProguardForDebug
不混淆的情況:
transformClassesWithDexForRelease
dofirst —— 遍曆輸入檔案,產生md5儲存好(hash.txt),然後注入代碼
transformClassesWithDexForDebug
dofirst —— 遍曆輸入檔案,產生md5,和hash對比,將改變過的類複製到補丁檔案夾,然後注入代碼
混淆的情況:
transformClassesAndResourcesWithProguardForRelease
dolast —— 遍曆輸出檔案,產生md5儲存好(hash.txt),然後注入代碼,將mapping儲存好
transformClassesAndResourcesWithProguardForDebug(需要使用applymapping)
dolast —— 遍曆輸出檔案,產生md5,和hash對比,將改變過的類複製到補丁檔案夾,然後注入代碼
開啟混淆後task的執行順序是proguard –> dex
因為dex永遠是在最後面執行,所以注入代碼和產生補丁這些操作都只需要hook dex就可以了
但是開啟混淆的時候,dex dofirst需要做的事情還是有點不同的,我們可以通過一個變數來控制 def minify = false
hook proguard,在proguardTransform執行的時候覆制minify = true
這樣就可以控制混淆和不混淆兩種情況了。
2.3 實際遇到的問題
1、 不clean項目,第二次運行release打包不會注入代碼
這是因為gradle的增量式構建,up-to-date,task不執行
解決方式:
dexRelease.outputs.upToDateWhen {false} 讓task一直都執行
http://stackoverflow.com/questions/7289874/resetting-the-up-to-date-property-of-gradle-tasks
2、如果有使用到自訂控制項,在xml的preView視窗會報null 指標異常
這是因為自訂控制項已經被注入了代碼,而預覽視窗的時候並沒有載入hack.jar,找不到AntilazyLoad.class,所以報null 指標。
解決方式:
使用pluginExtention,在build.gradle中組態變數,控制在debug模式下是否注入代碼。
,這裡添加了兩個Extention
3、如何applymapping
applymapping的作用是複用上一次的混淆規則。
所以我們需要將release產生的mapping.txt應用到debug的混淆上,否則可能無法正確的產生補丁。
解決方式:
第一種:
手動設定debug的混淆檔案
第二種:
1. 在gradle 1.5以下時,可以直接task.applyMapping(File file)的方式在代碼中動態添加
2. 在gradle1.5以上時,因為proguard的transform是一個特殊的task,所以並不能直接applyMapping,需要做一些強轉。
(proguardDebug即transformClassesAndResourcesWithProguardForDebug)
4、開啟混淆後的Release簽名打包,如果debug模式不開啟混淆的話,會將所有類都打包成補丁。
這是因為,如果debug模式不開啟混淆,那麼就會拿不混淆的代碼和Release已經混淆的代碼進行校正,md5肯定不一致,所以會將所有類打包成補丁包。
解決方式:
暫時沒有好辦法,老老實實開啟混淆吧。Debug是否開啟混淆要和Release保持一致
5、如何簽名補丁
補丁的簽名主要用到的是jdk的工具,jarsigner.exe。使用代碼調用命令列即可
6、如何進行簽名校正
首先,debug安裝的app不需要進行校正,這是檢測當前app是否是debug簽名的方法。
http://blog.csdn.net/luohai859/article/details/44679085
然後,這是校正補丁包和app簽名是否一致
http://blog.csdn.net/hudashi/article/details/8245105
7、android 6.0無法從sdcard載入補丁包
運行時許可權機制的問題,可以將補丁包放到app私人空間載入。
8、 androidStudio 2.0以上用到了instantRun,這是否會對debug自動產生補丁包產生影響。
這個問題我還沒有測試,如果真的有影響的話也有很簡單的解決方式,直接使用簽名打包debug也可以產生補丁包。
三、參考項目
https://github.com/jasonross/Nuwa
https://github.com/bunnyblue/DroidFix
https://github.com/Livyli/AndHotFix
主要是第三個,簽名校正的思路來源於它
四、寫在後面
終於算是完成了熱補丁架構了,其中過程真的累人啊!
整個架構的實現思路比較清晰簡單,代碼量也不超過1000行,很適合正在學習這個技術的朋友們。
求star,求star,第一個上傳到github的項目求star (??`ω′?)
https://github.com/AItsuki/HotFix
Android熱補丁動態修複技術(完結篇):自動產生打包帶簽名的補丁,重構項目