Android 熱修補方案(AndFix)

來源:互聯網
上載者:User

標籤:

AndFix介紹AndFix是一個Android App的線上熱補丁架構。使用此架構,我們能夠在不重複發版的情況下,線上修改App中的Bug。AndFix就是 “Android Hot-Fix”的縮寫。AndFix支援Android 2.3到6.0版本,並且支援arm 與 X86系統架構的裝置。完美支援Dalvik與ART的Runtime。AndFix 的補丁檔案是以 .apatch 結尾的檔案。AndFix是阿里的開源項目。https://github.com/alibaba/AndFix小例子:下載demo APK http://120.55.185.35:8080/old.apkdemo ,或者掃描下面二維碼,安裝好apk後運行AndFixDemo,點擊“提示資訊”按鈕,跳出“未修複的toast”,點擊“開始修複”,app會到遠程服務端下載補丁封裝更新,大概會持續幾秒鐘,等待幾秒後再次點擊“提示資訊”按鈕,會彈出修複好的內容

實際運行中則不需要點擊“開始修複”按鈕,在demo中為了對比效果,所以加了按鈕去控制實際運行中檢測需要打補丁的方案:
  1. 類似檢測升級,在開啟app或者某個頁面去檢測
  2. 服務端推送(需要和服務端定義更多實現細節)

 

AndFix的優點很明顯
  1. 補丁包很小(上面的例子,補丁包才幾K),打補丁的速度很快
  2. 打好補丁後,後續都不用再打了
使用方法:1. 添加依賴


 

2. 在自訂Application中初始化,並在AndroidManifest.xml中註冊該Application


 

3.在activity 中打補丁,(這裡省略了檢測步驟,預設就是直接下載補丁並更新,實際開發中需要對補丁的版本進行檢測,更新好後刪除補丁包,動態擷取補丁路徑等等)


 

4.到這裡已經完成了配置工作,接下來用正式的key的打包,就產生了old.apk, 把old.apk放到了伺服器上,項目就上線了

 

5.項目上線後,會有各種突發情況,(比如文案修改,緊急bug,造成app crash等麻煩,正常情況只有進行版本升級),這裡看下AndFix熱修補的步驟,以修改showToast方法為例子


 

6.現在把showToast方法修改,並打包,命名為fix.apk


 

7.下載apkpatch工具


 

8.製作補丁包,cd 到目錄下,運行apkpatch.bat -f fix.apk -t old.apk -o output1 -k demo.jks -p 123456 -a demo -e 123456

(這裡-t 為老的apk -f為修複過的apk,-o 為輸出目錄 -k 為打包的key -p -e 為密碼 -a 為別名)

 

9.螢幕輸出了增加了修改了toast的方法 ,同時目錄下新增了output1 目錄,點進去查看,其中.apatch為真正的補丁包


 

10. 用dex2Jar工具把diff檔案轉成jar檔案,在用jd.gui查看


其實這個工具就是比對兩個dex檔案,分析出修改過的地方,然後產生補丁包
 

11. 把.apatch命名為app.apatch上傳至伺服器,坐等用戶端打補丁


 

源碼分析:1首先來看下在application中的初始化
@Override    public void onCreate() {        super.onCreate();        // 初始化patch管理類        mPatchManager = new PatchManager(this);        // 初始化patch版本        mPatchManager.init("2.0");        // 載入已經添加到PatchManager中的patch        mPatchManager.loadPatch();    }

 

2 構造了PatchManager對象,來看下代碼
public PatchManager(Context context) {        mContext = context;        mAndFixManager = new AndFixManager(mContext); //初始化AndFixManager,等會再介紹          //getFileDir 擷取的是/data/data/<application package>/files        //在這裡是 /data/data/<application package>/files/apatch        mPatchDir = new File(mContext.getFilesDir(), DIR);        // 支援並發訪問的有序的補丁集合        mPatchs = new ConcurrentSkipListSet<Patch>();        // ClassLoader的集合,同樣也是基於安全執行緒的        mLoaders = new ConcurrentHashMap<String, ClassLoader>();    }

 

3 初始化patch版本,代碼
public void init(String appVersion) {        //再次檢測patch存放路徑        if (!mPatchDir.exists() && !mPatchDir.mkdirs()) {// make directory fail            Log.e(TAG, "patch dir create error.");            return;        } else if (!mPatchDir.isDirectory()) {// not directory            mPatchDir.delete();            return;        }        //用SharedPreferences 擷取path的版本        SharedPreferences sp = mContext.getSharedPreferences(SP_NAME,                Context.MODE_PRIVATE);        String ver = sp.getString(SP_VERSION, null);        if (ver == null || !ver.equalsIgnoreCase(appVersion)) {        //如果是第一次使用或者版本不一致,則刪除所有Patch ,這裡equalsIgnoreCase是忽略大小寫equals            cleanPatch();        // 存放版本             sp.edit().putString(SP_VERSION, appVersion).commit();        } else {        // 載入有所的Patch            initPatchs();        }    }    private void initPatchs() {        File[] files = mPatchDir.listFiles();        for (File file : files) {            addPatch(file);        }    }

 

4 載入已經添加到PatchManager中的patch,代碼
/**     * load patch,call when application start     * 這裡寫的也很清楚了,在程式啟動的時候調用     */    public void loadPatch() {        //首先載入了通用的類載入器        mLoaders.put("*", mContext.getClassLoader());// wildcard        Set<String> patchNames;        List<String> classes;        //遍曆每個補丁包  Patch 的結構HashMap<String, List<String>>()        //實際中,只會有一個key,就是你修改後apk的名字,list中存放修改的className        //fix----[cv.cocoa.com.andfixdemo.MainActivity_CF]        for (Patch patch : mPatchs) {            patchNames = patch.getPatchNames();            for (String patchName : patchNames) {                classes = patch.getClasses(patchName);                //更新補丁                mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),                        classes);            }        }    }

 

5.在Application中的初始化代碼就完成了,其中還有一個AndFixManager沒講,AndFixManager在構造前,會做一個相容性的檢測,放在了Compat 類中,代碼
public class Compat {    public static boolean isChecked = false;    public static boolean isSupport = false;    /**     * whether support on the device     * 需要對相容性進行檢測,檢測的判斷是不能是YunOs的手機,sdk的版本必須是在2.3-6.0之間     * @return true if the device support AndFix     */    public static synchronized boolean isSupport() {        if (isChecked)            return isSupport;        isChecked = true;        // AndFix.setup()判斷是Dalvik還是Art虛擬機器,來註冊Native方法        if (!isYunOS() && AndFix.setup() && isSupportSDKVersion()) {            isSupport = true;        }        if (inBlackList()) {            isSupport = false;        }        return isSupport;    }}
/**     * initialize     *     * @return true if initialize success     *      *  判斷是Dalvik還是Art虛擬機器,並初始化native的方法,     *      * 官方文檔中說到https://developer.android.com/guide/practices/verifying-apps-art.html     * You can verify which runtime is in use by calling System.getProperty("java.vm.version"). If ART is in use, the property‘s value is "2.0.0" or higher.     *       */    public static boolean setup() {        try {            final String vmVersion = System.getProperty("java.vm.version");            boolean isArt = vmVersion != null && vmVersion.startsWith("2");            int apilevel = Build.VERSION.SDK_INT;            return setup(isArt, apilevel);        } catch (Exception e) {            Log.e(TAG, "setup", e);            return false;        }    }
6.然後再來看下AndFixManager 的代碼
//最關鍵的就是這裡,擷取到Class對象後,用反射擷取修改的方法private void fixClass(Class<?> clazz, ClassLoader classLoader) {        //java反射擷取Class的方法        Method[] methods = clazz.getDeclaredMethods();        MethodReplace methodReplace;        String clz;        String meth;        for (Method method : methods) {            // 找到MethodReplace的註解            methodReplace = method.getAnnotation(MethodReplace.class);            if (methodReplace == null)                continue;            //對照上面的demo,clz就是cv.cocoa.com.andfixdemo.MainActivity            clz = methodReplace.clazz();              //對照上面的demo,meth 就是showToast            meth = methodReplace.method();            if (!isEmpty(clz) && !isEmpty(meth)) {                //替換方法                replaceMethod(classLoader, clz, meth, method);            }        }    }    /**     * replace method     *      * @param classLoader classloader     * @param clz class     * @param meth name of target method      * @param method source method     */    private void replaceMethod(ClassLoader classLoader, String clz,            String meth, Method method) {        try {            String key = clz + "@" + classLoader.toString();            Class<?> clazz = mFixedClass.get(key);            if (clazz == null) {// class not load                Class<?> clzz = classLoader.loadClass(clz);                // initialize target class                clazz = AndFix.initTargetClass(clzz);            }            if (clazz != null) {// initialize class OK                mFixedClass.put(key, clazz);                Method src = clazz.getDeclaredMethod(meth,                        method.getParameterTypes());                //調用jni的方法                AndFix.addReplaceMethod(src, method);            }        } catch (Exception e) {            Log.e(TAG, "replaceMethod", e);        }    }

Android 熱修補方案(AndFix)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.