AndFix,全稱是Android hot-fix。是阿里開源的一個熱補丁框架,容許APP在不從新發布版本的狀況下修復線上的bug。支持Android 2.3 到 6.0,而且支持arm 與 X86系統架構的設備。完美支持Dalvik與ART的Runtime,補丁文件是以 .apatch 結尾的文件。java
參考網站:android
github地址git
AndFix使用說明:github
http://www.jianshu.com/p/479b8c7ec3e3網絡
Alibaba-AndFix Bug熱修復框架原理及源碼解析 :架構
[http://blog.csdn.net/qxs965266509/article/details/49816007](http://blog.csdn.net/qxs965266509/article/details/49816007 " Alibaba-AndFix Bug熱修復框架原理及源碼解析 ")app
Andfix修復的總體流程:框架
AndFix的實現原理是方法的替換:ide
咱們以一個具體的例子來講明:
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文件夾下,
開始演示效果:
compile 'com.alipay.euler:andfix:0.3.1@aar'
建立一個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); } }
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
在須要加載補丁的地方調用:
PatchUtils.loadPatch(context);
使用工具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。
能夠看到在當前目錄下生成了相應的補丁文件:
補丁命名規則以下:
a_b.apatch
a表示versionCode,b表示當前的補丁的版本。
例如:若是當前的versionCode的版本爲4,補丁的版本爲3,則命名爲4_3.apatch。
Andfix並不能修復全部狀況下出現的bug,測試結果以下:
該Demo就是用來測試上述這12種狀況的。
例子中都是有bug的狀況,請讀者自行將bug修復,測試修復狀況,使用方法見上面的具體使用。
能夠放在自定義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); } }}
-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.** { *; }