Android熱補丁技術方案整理
來源:程序員人生 發布時間:2016-08-16 18:20:16 閱讀次數:3921次
概述
項目快速迭代進程中,不可避免的出現BUG,Android線上出現問題,通常需要發版解決。緊急發版,用戶不1定升級,強迫升級又不友好,有甚么更好的解決方案呢?這就用到了熱修復技術。
QQ團隊的hotfix
hotfix,后來發展成為RocooFix,
GitHub地址: https://github.com/dodola/HotFix
原理詳細介紹官方文章:安卓App熱補釘動態修復技術介紹
HotFix存在的問題:這類方法沒法在已加載好的類中實現動態替換,只能在類加載之前替換掉。就是說,補釘下載下來后,只能等待用戶重啟利用才能完成補釘效果。
RocooFix支持兩種模式:
靜態修復某種情況下需要重啟利用。
動態修復,無需重啟利用便可生效。
補釘制作
該技術的原理很簡單,其實就是用ClassLoader加載機制,覆蓋掉有問題的方法。所以我們的補釘其實就是有問題的類打成的1個包。
例子中的出現問題的類是 dodola.hotfix.BugClass
原始代碼以下:
public class BugClass {
public String bug() {
return "bug class";
}
}
我們假定BugClass
類里的bug()
方法出現毛病,需要修復,修復代碼以下:
public class BugClass {
public String bug() {
return "fixed class";
}
}
那末我們只需要將修復過的類編譯后打包成dex便可
步驟以下:
-
將補釘類提取出來到1個文件夾里

-
將class文件打入1個jar包中 jar cvf path.jar *
- 將jar包轉換成dex的jar包
dx --dex --output=path_dex.jar path.jar
這樣就生成了補釘包path_dex.jar

實現javassist動態代碼注入
實現這1部份功能的緣由主要是由于出現以下異常
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
問題緣由在文檔中已描寫的比較清楚。
就是如果以上方法中直接援用到的類(第1層級關系,不會進行遞歸搜索)和clazz都在同1個dex中的話,那末這個類就會被打上CLASS_ISPREVERIFIED
很明顯,解決的方法就是在類中援用1個其他dex中的類,但是源碼方式的援用會將援用的類打入同1個dex中,所以我們需要找到1種既能編譯通過并且將兩個相互援用的類分離到不同的dex中,因而就有了這個動態的代碼植入方式。
首先我們需要制作援用類的dex包,代碼在hackdex
中,我直接使用了文檔中的類名 AntilazyLoad
這樣可以和文章中對應起來,方便1些。
我們將這個庫打包成dex的jar包,方法跟制作補釘1樣。
下面是重點,我們要用javassist
將這個類在編譯打包的進程中插入到目標類中。
為了方便,我將這個進程做成了1個Gradle的Task,代碼在buildSrc
中。
這個項目是使用Groovy開發的,需要配置Groovy SDK才可以編譯成功。
核心代碼以下:
/**
* 植入代碼
* @param buildDir 是項目的build class目錄,就是我們需要注入的class所在地
* @param lib 這個是hackdex的目錄,就是AntilazyLoad類的class文件所在地
*/
public static void process(String buildDir, String lib) {
println(lib)
ClassPool classes = ClassPool.getDefault()
classes.appendClassPath(buildDir)
classes.appendClassPath(lib)
//下面的操作比較容易理解,在將需要關聯的類的構造方法中插入援用代碼
CtClass c = classes.getCtClass("dodola.hotfix.BugClass")
println("====添加構造方法====")
def constructor = c.getConstructors()[0];
constructor.insertBefore("System.out.println(dodola.hackdex.AntilazyLoad.class);")
c.writeFile(buildDir)
CtClass c1 = classes.getCtClass("dodola.hotfix.LoadBugClass")
println("====添加構造方法====")
def constructor1 = c1.getConstructors()[0];
constructor1.insertBefore("System.out.println(dodola.hackdex.AntilazyLoad.class);")
c1.writeFile(buildDir)
growl("ClassDumper", "${c.frozen}")
}
下面在代碼編譯完成,打包之前,履行植入代碼的task就能夠了。
在 app 項目的 build.gradle 中插入以下代碼
task('processWithJavassist') << {
String classPath = file('build/intermediates/classes/debug')//項目編譯class所在目錄
dodola.patch.PatchClass.process(classPath, project(':hackdex').buildDir
.absolutePath + '/intermediates/classes/debug')//第2個參數是hackdex的class所在目錄
}
android{
.......
applicationVariants.all { variant ->
variant.dex.dependsOn << processWithJavassist //在履行dx命令之前將代碼打入到class中
}
}
反編譯編譯后的apk可以發現,代碼已植入進去,而且包里其實不存在dodola.hackdex.AntilazyLoad
這個類

阿里巴巴的AndFix
GitHub地址: https://github.com/alibaba/AndFix
使用步驟
初始化
patchManager = new PatchManager(context);
patchManager.init(appversion);//current version
加載patch
patchManager.loadPatch();
添加patch文件
patchManager.addPatch(path);
支持熱更新,不需要重新啟動
阿里巴巴的dexposed
GitHub地址: https://github.com/alibaba/dexposed
大眾點評的Nuwa
Nuwa的具體實現也是根據QQ空間的熱修復方案來實現的
GitHub地址: https://github.com/jasonross/Nuwa
DroidFix
GitHub地址: https://github.com/bunnyblue/DroidFix
官方介紹: http://bunnyblue.github.io/DroidFix/
DroidFix的實現原理跟QQ空間的熱補釘方案類似。
攜程的DynamicAPK
GitHub地址: https://github.com/CtripMobile/DynamicAPK
主流熱補釘開源方案基本上就是以上這些。
歡迎掃描2維碼,關注公眾號

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈