Android 熱更新——非侵入AOP架構,android侵入aop架構
Android 用戶端應用上線以後,一旦出現Bug,一般的解決思路是發修複包升級應用,這種方式不僅耗時,更重要的是使用者需要頻繁的升級版本,體驗不好,所以最佳化的思路是在不發版本的情況下熱更新,以期提高使用者體驗。
近期GitHub新出一種非侵入運行期AOP架構Dexposed, 下面簡單瞭解一下這個架構,GitHub地址。
簡要說明:
該架構基於AOP思想,支援經典的AOP使用情境,可應用於日誌記錄,效能統計,安全控制,交易處理,異常處理等方面。
針對Android平台,Dexposed支援函數層級的線上熱更新,例如對已經發布在應用市場上的宿主APK,當我們從crash統計平台上發現某個函數調用有bug,導致經常性crash,這時,可以在本地開發一個補丁APK,並發布到伺服器中,宿主APK下載這個補丁APK並整合後,就可以很容易修複這個crash。
Dexposed是基於久負盛名的開源Xposed架構實現的一個Android平台上功能強大的無侵入式運行時AOP架構。Dexposed的AOP實現是完全非侵入式的,沒有使用任何註解處理器,編織器或者位元組碼重寫器。
Patch原理
首先從GitHub上拉下來代碼有幾個坑需要注意:
- 如果想直接拿過來用,你可能會失望而歸,因為patchsample這個android程式就沒有入口Activity,所以需要自行建立。
- 注意Lib包如何添加,不能直接添加在libs這個工程自建的檔案中,應該放在lib檔案中,並手動添加java build依賴
- 注意使用過程中不是像GitHub上那樣介紹的如此精簡,需要自行添加sunApkPatch代碼並適當添加捕獲異常操作,防止Patch包引起Crash。
- patchsample只是個簡單的patch,如果需要增加比較複雜的patch,需要有依賴關係,為了縮小patch體積,這個依賴只能是部分依賴,所以推薦方式是將需要補丁的class檔案打jar包引入依賴即可。
接下來我們看看具體的流程:
首先需要我們動態監測AOP環境
runPatchApk();
這裡需要注意的是PatchMain.load()這個方法,該方法的主要用途是載入patch APK的所有類,並將實現IPatch的類添加到List中去,然後通過匹配載入的類或者類方法來實現非侵入式AOP。
public void runPatchApk() { if (android.os.Build.VERSION.SDK_INT == 21) { return; } if (!DexposedBridge.canDexposed(this)) { Log.d("Hotpatch", "This device doesn't support dexposed!"); return; } File cacheDir = getExternalCacheDir(); if (cacheDir != null) { String fullpath = cacheDir.getAbsolutePath() + File.separator + "PATCH_NAME.apk"; PatchResult result = PatchMain.load(this, fullpath, null); if (result.isSuccess()) { Log.e("Hotpatch", "patch success!"); } else { Log.e("Hotpatch", "patch error is " + result.getErrorInfo()); } }}
Patch實踐
public class Activity extends BaseSherlockSubActivity implements OnNewIconUIRefreshListener { private void showDialog() { final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Dexposed sample") .setMessage("Please clone patchsample project to generate apk, and copy it to \"/Android/data/PACKAGE_NAME/cache/PATCH_NAME.apk\"") .setPositiveButton("ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { } }).create().show(); }}
假如我們上線的代碼中如上所示,在彈層中出現文案bug,那麼該如何熱更新。
代碼修複操作在Patch工程中,添加如下代碼:
public class DialogPatch implements IPatch { @Override public void handlePatch(final PatchParam arg0) throws Throwable { Class<?> cls = null; try { cls = arg0.context.getClassLoader().loadClass("com.android.activity.Activity"); } catch (ClassNotFoundException e) { e.printStackTrace(); return; } DexposedBridge.findAndHookMethod(cls, "showDialog", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { final Activity mainActivity = (Activity) param.thisObject; AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity); builder.setTitle("Fanli Dexposed sample").setMessage("The dialog is shown from patch apk!").setPositiveButton("ok", new OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { Class<?> clsInner; try { clsInner = arg0.context.getClassLoader().loadClass("com.android.activity.OutObject"); } catch (ClassNotFoundException e) { e.printStackTrace(); return; } try { OutObject outObject = (OutObject) clsInner.newInstance(); if (outObject.callFromOutMethod()) { AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity); builder.setTitle("Fanli Dexposed sample").setMessage("com.android.activity.OutObject is Worked!") .setPositiveButton("ok", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).create().show(); } } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }).create().show(); return null; } }); }}
然後將這個patch APK 傳到Server,在主APK中通過下載patch apk到指定目錄,然後動態監測AOP環境並loadPatch即可實現熱更新。
接下來如果應用到實際項目中需要完善的有以下幾點:
Patch測試結果:
基於以上實現方案測試的環境包括:
Dalvik 4.0-4.4均已經通過
目前 ART 5.0 以及以上版本 尚未通過。(待更新Native包和Jar包)
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。