簡介
已經上線的項目發現BUG,緊急修復BUG發佈新版本?No,也許你需要AndFix。
AndFix 是阿里巴巴開源的 Android 應用熱修復工具,幫助 Anroid 開發者修復應用的線上問題。Andfix 是 “Android hot-fix” 的縮寫。支持 Android 2.3 - 6.0,ARM 和 x86 架構,dalvik 運行時和 art 運行時。AndFix 的分支是 .apatch 文件。
GitHub地址 : https://github.com/alibaba/AndFix
原理圖:
使用概述
1. 添加依賴
dependencies {
compile 'com.alipay.euler:andfix:0.3.1@aar'
}
2 . 儘可能早的加載補丁
package com.dyk.andfixtest;
import android.app.Application;
import android.content.pm.PackageManager;
import com.alipay.euler.andfix.patch.PatchManager;
/**
* Created by dyk on 2016/3/24.
*/
public class MyApplication extends Application {
private static MyApplication instance;
private PatchManager patchManager;
public static MyApplication getInstance(){
return instance;
}
@Override
public void onCreate() {
super.onCreate();
instance = this;
// 初始化patch管理類
patchManager = new PatchManager(this);
String appVersion = null;
try {
appVersion = getPackageManager().getPackageInfo(getPackageName(),0).versionName;
// 初始化patch版本
patchManager.init(appVersion);
// 加載已經添加到PatchManager中的patch
patchManager.loadPatch();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
public PatchManager getPatchManager() {
return patchManager;
}
}
每次appVersion變更都會導致所有補丁被刪除,如果appversion沒有改變,則會加載已經保存的所有補丁。
3. 加載新補丁
MyApplication.getInstance().getPatchManager().addPatch(patchPath);
詳細使用教程
1 . 添加依賴
2. 儘可能早的加載補丁
3. 修復bug,生成沒有bug的apk文件
4. 對比新舊apk生成.apatch補丁文件
5. 加載新補丁,修復bug
前兩步在使用概述中已經說明,不在贅述。修復bug,生成新apk也和正常一樣。這裏注意一個地方:生成新舊apk要使用同一個.jks簽名文件。下面是一個示例
從一個Demo開始
製造bug
下面的代碼假設在waitFix()裏存在bug(Log.i(TAG, “waitFix bug”),我們目標是不發佈新版本,改變這行log)
package com.dyk.andfixtest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.Window;
import java.io.IOException;
public class MainActivity extends Activity {
private static final String TAG = "AndFix";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
findViewById(R.id.showLogBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
waitFix();
}
});
findViewById(R.id.fixBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String patchPath = Environment.getExternalStorageDirectory()+"/tmp/first.apatch";
try {
MyApplication.getInstance().getPatchManager().addPatch(patchPath);
Log.i(TAG,"fix bug, please reClick");
} catch (IOException e) {
e.printStackTrace();
Log.i(TAG,"error:"+e.toString());
}
}
});
}
// 假設待修復的Bug在此方法中
private void waitFix() {
Log.i(TAG,"waitFix bug");
}
}
因爲要讀取.apatch文件,不要忘了添加限權。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
此時我們簽名運行apk,生成bug.apk。
// 已修復
private void waitFix() {
Log.i(TAG,"fix bug");
}
修改bug後生成fix.apk。
生成.aptch補丁文件需要一個工具,點我下載。現在打開命令行(cmd),進入從剛下載工具的tools文件夾(我的是D:\Temp\AndFix\AndFix-master\tools,命令行爲:d:回車,cd D:\Temp\AndFix\AndFix-master\tools)。將bug.apk、fix.apk和簽名文件放入tools文件夾下。輸入命令
apkpatch -o D:\Temp\AndFix\output -k AndFix.jks -p admin888 -a 亞洲 -e admin888 -f fix.apk -t bug.apk
字段說明:
-o <output> : 輸出目錄
-k <keystore>: 打包所用的keystore
-p <password>: keystore的密碼
-a <alias>: keystore 用戶別名
-e <alias password>: keystore 用戶別名密碼
-f <new.apk> :新版本
-t <old.apk> : 舊版本
看見如上提示即生成.apatch文件成功。進入輸出目錄(-o 後面)。可以看到一個smail文件夾、diff.dex和一個.apatch文件。經常反編譯別人apk的同學一定對smail文件夾不陌生。可以查看到代碼,可惜才疏學淺看不懂.smail文件。這裏我們要用到的是.apatch文件。
1. 更改.apatch文件名爲first.apatch。
2. 安裝bug.apk,真是個不吉利的名字。
3. 將first.apatch放入MainActivity裏的patchPath路徑下(我這裏是/tmp/)。
4. 運行bug.apk
依次點擊showLogBtn、fixBtn、showLogBtn。Log輸出如下
可以看到Log輸出已經改變,這也意味着帶有Bug的方法被成功修復。
多次打補丁
如果本地保存了多個補丁,那麼AndFix會按照補丁生成的時間順序加載補丁。具體是根據.apatch文件中的PATCH.MF的字段Created-Time。
混淆
-keep class * extends java.lang.annotation.Annotation
-keepclasseswithmembernames class * {
native <methods>;
}
探索
1. 修改first.apatch後綴名爲.zip
2. 解壓文件
打開META-INF中的文件PATCH.MF,可以看到如下內容
Manifest-Version: 1.0
Patch-Name: fix
Created-Time: 25 Mar 2016 08:28:00 GMT
From-File: fix.apk
To-File: bug.apk
Patch-Classes: com.dyk.andfixtest.MainActivity_CF
Created-By: 1.0 (ApkPatch)
可以發現Patch-Classes就是被改動過的文件,後面還加個_CF(CrossFire ?)。
使用dex2jar反編譯classes.dex
1. 下載dex2jar和jd-gui,點我下載
2. 將classes.dex文件複製到dex2jar解壓目錄
3. 命令行進入dex2jar解壓目錄
4. 輸入命令:d2j-dex2jar classes.dex
經過四招還我漂漂拳後,可以在dex2jar解壓目錄下發現classes-dex2jar.jar這麼一個文件。
使用jd-gui打開classes-dex2jar.jar
package com.dyk.andfixtest;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.alipay.euler.andfix.annotation.MethodReplace;
public class MainActivity_CF extends Activity
{
private static final String TAG = "AndFix";
@MethodReplace(clazz="com.dyk.andfixtest.MainActivity", method="waitFix")
private void waitFix()
{
Log.i("AndFix", "fix bug");
}
protected void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
requestWindowFeature(1);
setContentView(2130968601);
findViewById(2131492944).setOnClickListener(new MainActivity.1(this));
findViewById(2131492943).setOnClickListener(new MainActivity.2(this));
}
}
可以發現.patch只是差異文件,這裏給需要替換的方法添加了一個指定class和method的註解@MethodReplace(clazz="com.dyk.andfixtest.MainActivity", method="waitFix")
。客戶端sdk得到補丁文件後根據註解尋找需要替換的方法。最後由JNI層完成方法的替換。