Andfix和hotfix是兩種android熱修復框架。php
android的熱修復技術我看的最先的應該是QQ空間團隊的解決方案,後來真正須要了,才仔細調查,如今的方案中,阿里有兩種Dexposed和Andfix框架,因爲前一種不支持5.0以上android系統,因此阿里系的方案咱們就看Andfix就好。Hotfix框架算是對上文提到的QQ空間團隊理論實現。本文旨在寫實現方案,捎帶原理。java
Andfixandroid
引入git
框架官網:https://github.com/alibaba/AndFix
介紹是用英文寫的,因此附上翻譯網址:
http://blog.csdn.net/qxs965266509/article/details/49802429github
使用android studio開發,引入以下:瀏覽器
compile 'com.alipay.euler:andfix:0.4.0@aar'
原理服務器
下面是個修復的過程圖,供咱們更好地理解。app
能夠看出,andfix的修復是方法級的,對有bug的方法進行替換。框架
作補丁eclipse
官方有給使用方式,不過比較簡略,因此會有些修改。個人思路是把補丁製做好,而後放到服務器上,客戶端下載補丁到指定文件夾,而後修復。
首先要有補丁的製做工具,官方也爲咱們準備好了:這裏
解壓後,咱們把修復前的apk和修復後的apk,keystore(爲了方便,我就用debug的keystore了)放到這個文件夾裏,以下:
其中須要用命令作補丁文件,就是須要一個修復前的apk和修復後的apk作對比,命令含義以下:
命令 : apkpatch.bat -f new.apk -t old.apk -o output1 -k debug.keystore -p android -a androiddebugkey -e android
-f <new.apk> :新版本
-t <old.apk> : 舊版本
-o <output> : 輸出目錄
-k <keystore>: 打包所用的keystore
-p <password>: keystore的密碼
-a <alias>: keystore 用戶別名
-e <alias password>: keystore 用戶別名密碼
而後會在outputdic裏生成一個後綴是.apatch的文件:
更名成out.apatch,這就是咱們的補丁。
打補丁
如何使用補丁呢?和把大象裝進冰箱是同樣步驟。
下面直接上代碼了:
第一步:把補丁放到服務器。
簡單起見,用的xampp,寫了段php代碼,起到下載的功能就能夠了。
<?php
$file_name = "out.apatch";//須要下載的文件
define("SPATH","/files/");//存放文件的相對路徑
$file_sub_path = $_SERVER['DOCUMENT_ROOT'];//網站根目錄的絕對地址
$file_path = $file_sub_path.SPATH.$file_name;//文件絕對地址,即前面三個鏈接
//判斷文件是否存在
if(!file_exists($file_path)){
echo "該文件不存在";
return;
}
$fp = fopen($file_path,"r");//打開文件
$file_size = filesize($file_path);//獲取文件大小
/*
*下載文件須要用到的header
*/
header("Content-type:application/octet-stream");
header("Accept-Ranges:bytes");
header("Accept-Length:".$file_size);
header("Content-Disposition:attachment;filename=".$file_name);
$buffer=1024;
$file_count=0;
//向瀏覽器返回數據
while(!feof($fp) && $file_count<$file_size){
$file_con = fread($fp,$buffer);
$file_count += $buffer;
echo $file_con;//這裏若是不echo,只會下載到0字節的文件
}
fclose($fp);
?>
第二步:下載和打補丁。
回到android,在咱們的application裏:
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
YuanAndfix.inject(this);
}
}
其中,YuanAndfix類:
public class YuanAndfix {
public static final String apatch_path = "out.apatch";
public static void inject(final Context context) {
final PatchManager patchManager = new PatchManager(context);
patchManager.init(BuildConfig.VERSION_CODE + "");//current version
patchManager.loadPatch();
new Thread(new Runnable() {
@Override
public void run() {
HttpDownload httpDownload = new HttpDownload();
httpDownload.downFile("http://192.168.1.12/download.php", context.getDir("patch", Context.MODE_PRIVATE).getAbsolutePath()+"/",apatch_path);
try {
String patchPath =context.getDir("patch", Context.MODE_PRIVATE).getAbsolutePath()+"/"+apatch_path;
File file = new File(patchPath);
if (file.exists()) {
patchManager.addPatch(patchPath);
Toast.makeText(context,"打補丁完成",Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context,"失敗",Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
這樣,熱修復就完成了,我這個例子是點擊按鈕,彈出toast顯示文字,修復前是
Toast.makeText(MainActivity.this,"bug",Toast.LENGTH_SHORT).show();
修復後是:
Toast.makeText(MainActivity.this,"fixed",Toast.LENGTH_SHORT).show();
以上就是Andfix的使用,通過個人試驗,使用這個框架的侷限在於不能修改全局變量,不能加新的方法,不過能夠在現有的方法上作修改,加局部變量。從這方面來看,Andfix其實要求咱們只是修改方法裏面的bug,不能大規模作更改。若是咱們以爲這種修復不能知足修復要求,那麼,能夠看另外這種,侷限更少的熱修方案。
HotFix
原理
官網:https://github.com/dodola/HotFix
在用這個框架以前,我但願你先去看一下原理,對後面的實現有很大幫助。
下面我簡單說一下原理。
把多個dex文件塞入到app的classloader之中android加載的時候,若是有多個dex文件中有相同的類,就會加載前面的類,因此這個熱補的原理就是把有問題的類替換掉,把須要的類放到最前面,達到熱補的目的。
可是有個問題,咱們想要替換的類,不能被打上CLASS_ISPREVERIFIED標誌,不然回報錯,因而這個方案的難點就在於如何讓想要被修復的類不被打上CLASS_ISPREVERIFIED標誌。因此,大神們的hack神計來了,先製做一個dex包,而後給咱們想要修復的類的構造方法,都注入這個dex包,其實就是輸出這個dex包的一個類:
System.out.println(dodola.hackdex.AntilazyLoad.class);
這樣,就可讓咱們想要修復的類不被打上CLASS_ISPREVERIFIED標誌,而後就能夠加載補丁了。
框架
這個框架的使用不論是配置上,仍是補丁生成上,都相對麻煩一些,雖然有個類似的框架Nuwa作了自動化這塊,不過聽說有些坑沒人填,因此果斷用這個hotfix框架。框架下載下來,咱們先看一下結構。
app是主工程;
buildSrc是Gradle的Task,Gradle的編譯命令就是由多個task組成的,說白了就是Gradle在編譯程序的時候會按照這些task順序執行命令。
hackdex裏面就一個空類,目的爲了讓編譯經過,讓主工程的類不被打上CLASS_ISPREVERIFIED標誌。
hotfixlib是個修復的工具類。
接着,咱們看一下他們是怎麼一塊兒工做的。
首先是主工程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')//第二個參數是hackdex的class所在目錄
}
和
applicationVariants.all { variant ->
variant.dex.dependsOn << processWithJavassist //在執行dx命令以前將代碼打入到class中
}
這就是經過javassist,給主工程的類的構造方法注入
System.out.println(dodola.hackdex.AntilazyLoad.class);
AntilazyLoad.class在app的assets中,程序運行後會拷貝到sd卡里,主要是爲了讓主工程的類不被打上CLASS_ISPREVERIFIED標誌。
作補丁
補丁就是想要替換的類的class文件的集合,補丁製做過程參考
https://github.com/dodola/HotFix;
其中用到的類在這裏提早:
接着把修復好的類放到一個文件夾,文件夾路徑得和你原來類的包名一致。如:
好比上圖的BugClass.class類,就放到這樣的文件夾
而後執行命令:
這樣就生成了一個path.jar在d盤下,接着就是把這個jar作成dex的jar了,因爲要用到dx,而這個dx在咱們的sdk工具包裏,因此我把這個path.jar拷貝到sdk工具包,利用dx命令
而後會生成path_dex.jar,這就是咱們的補丁文件了。
打補丁
public class HotfixApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
File dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "hackdex_dex.jar");
Utils.prepareDex(this.getApplicationContext(), dexPath, "hackdex_dex.jar");
HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hackdex.AntilazyLoad");
try {
this.getClassLoader().loadClass("dodola.hackdex.AntilazyLoad");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
而後是下載和打補丁
switch (item.getItemId()) {
case R.id.action_fix: {
new Thread(new Runnable() {
@Override
public void run() {
String url = "http://192.168.1.12/download.php";
HttpDownload httpDownload = new HttpDownload();
final int flag = httpDownload.downFile(url, MainActivity.this.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath()+"/", "path_dex.jar");
HotFix.patch(MainActivity.this, MainActivity.this.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath()+"/"+"path_dex.jar", "");
runOnUiThread(new Runnable() {
@Override
public void run() {
String fileState=null;
if (flag==0) {
fileState = "下載完成";
} ;
if (flag==1) {
fileState = "文件已存在";
}
if (flag==-1) {
fileState = "下載錯誤";
}
Toast.makeText(MainActivity.this, fileState, Toast.LENGTH_SHORT).show();
}
});
}
}).start();
}
break;
case R.id.action_test:
LoadBugClass bugClass = new LoadBugClass();
Toast.makeText(this, "測試調用方法:" + bugClass.getBugString(), Toast.LENGTH_SHORT).show();
break;
}
這裏須要注意,若是類一旦調用過,須要下次啓動程序補丁纔會生效。因此若是咱們先點了測試,再點下載,那麼須要重啓程序(後臺殺死),補丁纔會生效。
手動注入
上面關於防止類被打上CLASS_ISPREVERIFIED標誌的辦法雖然好,可是是有侷限性的,必需要用gradle編譯,還得了解字節碼注入,若是咱們是用eclipse開發,那就不能用了,其實咱們還有一種辦法,就是手動給類添加那行
System.out.println(dodola.hackdex.AntilazyLoad.class)代碼,只要保證編譯經過就能夠了。因此這裏這麼辦,咱們新建一個工程,androidstudio的話,
看main下,咱們新建了個hack文件夾,裏面放了個hack.jar,裏面只有這麼個類:
public class AntilazyLoad {
}
而後,在咱們主工程app裏面的類的構造方法,加入
System.out.println(dodola.hackdex.AntilazyLoad.class),這行代碼,就達到了手動注入的目的,就不須要那些複雜的task代碼,字節碼注入等操做。因此若是你是用eclipse的話,目錄就是這樣
這個jar包不會被打包進app,就是讓編譯經過,真正的AntilazyLoad.class其實仍是在項目的assets包下的hack_dex.jar。
上述方法都是親測徹底可行的,特別是這種手動注入的方法,能解決大部分開發者不會用熱更的困擾。這個辦法我是看這篇文章學到的。
PS:
一、這個框架不能修改用final修飾過得東西,切記。
二、官網給出的打補丁代碼
HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hackdex.BugClass");
這麼看的話,很不合理,第三個參數竟然要傳bug類名,咱們又不能預知哪一個類會發生bug,因此我改爲這樣
HotFix.patch(this, dexPath.getAbsolutePath(), "");
第三個參數不要了,親測,也是好使的。
總結
對比兩種解決方案,阿里的andfix更注重於改細節的bug,雖然它是從native層作得操做,可是框架封裝的很好,咱們使用起來很簡便,並且有更新維護,聽說阿里系的app打算都用這個。若是咱們僅僅就是開發一款app,尚未大改動,不會熱更全局變量,不會增長方法,那麼這個框架就是首選。 可是有的時候咱們可能開發的是一款sdk,譬如友盟sdk之類,或者想熱更全局變量,增長方法,那麼andfix能夠說是用不到的,因此這個時候hotfix是更好的選擇。 下載點這裏 Andfixdemo HotFixdemo 服務端PHP代碼