AndFix使用說明
AndFix,全稱是Android hot-fix。是阿里開源的一個熱補丁框架,允許APP在不重新發布版本的情況下修復線上的bug。支持Android 2.3 到 6.0,並且支持arm 與 X86系統架構的設備。完美支持Dalvik與ART的Runtime,補丁文件是以 .apatch 結尾的文件。
參考網站:
AndFix使用說明:
http://www.jianshu.com/p/479b8c7ec3e3
Alibaba-AndFix Bug熱修復框架原理及源碼解析 :
http://blog.csdn.net/qxs965266509/article/details/49816007
Andfix修復的整體流程:
AndFix的實現原理是方法的替換:
具體使用:
我們以一個具體的例子來說明:
demo測試activity生命周期函數界面很簡單,只是一個textView加button,當前textView顯示的是有bug,那么點擊button之后,就會加載補丁,textView就會顯示bug第一次被修復了。
先給出TestActivityLifeCycle.java:
package com.tulipsport.android.andfixdemos.test_activity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import com.tulipsport.android.andfixdemos.R;
import com.tulipsport.android.andfixdemos.utils.PatchUtils;
public class TestActivityLifeCycle extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView text=(TextView)findViewById(R.id.test);
text.setText("有bug");
findViewById(R.id.button).setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
PatchUtils.loadPatch(getApplicationContext());
}
});
}
}
按照下面1-3步執行,然后打包生成一個“有bug”的apk文件,我們將它命名為1.apk。
然后,將上面的 text.setText("有bug");
換成 text.setText("bug第一次被修復了");
再次打包,生成apk文件,我們將它命名為2.apk。然后使用apkpatch工具,根據1.apk和2.apk生成補丁文件,有關 補丁文件的生成操作和命名方式,見5-6.
假設我們生成的補丁為2_1.apatch,現在來演示效果。
將有bug的apk安裝到模擬器上:
打開app:顯示有bug
我們將補丁文件放在sdcard根目錄下的tulipsport_patches文件夾下,
開始演示效果:
1.使用gradle添加依賴
compile 'com.alipay.euler:andfix:0.3.1@aar'
2.使用PatchUtils
創建一個PatchUtils的工具類,用於加載補丁。
public class PatchUtils{
private static final String TAG="euler";
private static final String TULIPSPORT_PATCHES="/tulipsport_patches";
private static final String DIR="apatch";//補丁文件夾
/**
* patch manager
*/
public static PatchManager mPatchManager;
public static void loadPatch(Context context){
mPatchManager=new PatchManager(context);
mPatchManager.init(getVersionName(context));
mPatchManager.loadPatch();
try {
File dir=new File(Environment.getExternalStorageDirectory()
.getAbsolutePath() + TULIPSPORT_PATCHES);
String loadPatchName=Environment.getExternalStorageDirectory()
.getAbsolutePath() + TULIPSPORT_PATCHES + "/" +
String.valueOf(getVersionCode(context))
+ "_" +
FileUtils.getLoadPatchName(dir,
"apatch",String.valueOf(getVersionCode(context))) + ".apatch";
Log.d("loadPatchName",loadPatchName);
mPatchManager.addPatch(loadPatchName);
Log.d(TAG,"apatch:" + loadPatchName + " added.");
//復制且加載補丁成功后,刪除下載的補丁
File f=new File(context.getFilesDir(),DIR);
if (f.exists()) {
boolean result=new File(loadPatchName).delete();
if (!result)
Log.e(TAG,loadPatchName + " delete fail");
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static int getVersionCode(Context context){
try {
PackageInfo pi=context.getPackageManager().getPackageInfo(context.getPackageName(),0);
return pi.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return 0;
}
}
private static String getVersionName(Context context){
try {
PackageInfo pi=context.getPackageManager().getPackageInfo(context.getPackageName(),0);
return pi.versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return null;
}
}}
注意:補丁的下載位置為sdcard根目錄下的tulipsport_patches文件夾,可以自行修改。
PatchUtils中使用了FileUtils類,在這里簡單給出FileUtils:
package com.tulipsport.android.andfixmorebugs;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Brooks on 2016/3/9.
*/
public class FileUtils{
/**
* @param fileDir 文件目錄
* @param fileType 后綴名
* @return 特定目錄下的所有后綴名為fileType的文件列表
*/
public static List<String> getFiles(File fileDir,String fileType) throws Exception{
List<String> lfile=new ArrayList<String>();
File[] fs=fileDir.listFiles();
for (File f : fs) {
if (f.isFile()) {
if (fileType
.equals(f.getName().substring(
f.getName().lastIndexOf(".") + 1,
f.getName().length())))
lfile.add(f.getName());
}
}
return lfile;
}
public static String getLoadPatchName(File fileDir,String fileType,String versionCode) throws Exception{
List<String> files=getFiles(fileDir,fileType);
int maxPatchVersion=0;
for (String name : files) {
if (name.startsWith(versionCode + "_")) {
int patchVersion=Integer.valueOf(name.substring(name.indexOf("_") + 1,name.indexOf(".")));
maxPatchVersion=Math.max(maxPatchVersion,patchVersion);
}
}
return String.valueOf(maxPatchVersion);
}
}
3.添加權限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
4.加載補丁
在需要加載補丁的地方調用:
PatchUtils.loadPatch(context);
5.生成補丁
使用工具apkpatch-1.0.3
下載地址:https://github.com/alibaba/AndFix/raw/master/tools/apkpatch-1.0.3.zip
使用命令apkpatch生成補丁。
圖示參數缺一不可,否則無法生成補丁!!!
例如:
舊的apk為1.apk,新的apk為2.apk, -o表示補丁的輸出目錄,-k表示keystore, -p表示keystore的密碼,-a表示alias, -e表示entry password。
可以看到在當前目錄下生成了相應的補丁文件:
補丁命名規則如下:
6.補丁的命名規則
a_b.apatch
a表示versionCode,b表示當前的補丁的版本。
例如:如果當前的versionCode的版本為4,補丁的版本為3,則命名為4_3.apatch。
7.適用環境說明
Andfix並不能修復所有情況下出現的bug,測試結果如下:
該Demo就是用來測試上述這12種情況的。
例子中都是有bug的情況,請讀者自行將bug修復,測試修復情況,使用方法見上面的具體使用。
8.補丁加載的時機
可以放在自定義Application的onCreate方法中,也可以放在button的點擊事件中,也可以放在監聽網絡變化的廣播中。
例如:
放在自定義Application中:
package com.tulipsport.android.andfixmorebugs;
import android.app.Application;
/**
* Created by Brooks on 2016/3/4.
*/
public class MyApplication extends Application{
@Override
public void onCreate(){
super.onCreate();
PatchUtils.loadPatch(getApplicationContext());
}
}
或者放在監聽網絡變化廣播中:
public class MyReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context,Intent intent){
ConnectivityManager connectivityManager=(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mobNetInfo=connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
NetworkInfo wifiNetInfo=connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (!mobNetInfo.isConnected() && !wifiNetInfo.isConnected()) {
Toast.makeText(context,"網絡不可以用",Toast.LENGTH_SHORT).show();
//改變背景或者 處理網絡的全局變量
} else {
//改變背景或者 處理網絡的全局變量
//這里開始執行下載補丁操作,下載完成后,開始加載補丁
Toast.makeText(context,"開始加載補丁",Toast.LENGTH_SHORT).show();
PatchUtils.loadPatch(context);
}
}}
9.補丁加載流程
10.混淆
-printmapping proguard.map
首先需要生成mapping文件記錄混淆規則,之后可以把printmapping 這句話注釋掉,每次只使用applymapping。
-applymapping proguard.map
然后在下面加上
-keep class * extends java.lang.annotation.Annotation
-keepclasseswithmembernames class * {
native <methods>;
}
-keep class com.alipay.euler.andfix.** { *; }
11.局限性
- 無法添加新類和新的字段