關於Android APP在線熱修復bug方案的調研(二)(MultiDex的原理分析---Nuwa)

通過上一篇的分析,要進行在線修復bug,鎖定的兩套開源項目是:AndFix與Nuwa.

上篇文章對AndFix的實現機制進行了分析。


也提到了AndFix對static的支持不太好,下面是試驗的Demo:

添加了一個靜態的字段addString:


通過AndFix來製作patch會直接報錯:



而同樣的Demo使用Nuwa來製作Patch是沒有問題的,下面是運行的結果:



那麼採用Multi dex來進行hot fix的原理是什麼?

上一篇中也提到了Nuwa是採用的Muxdex的方案,那麼什麼時Multi dex呢?

網上搜索一下就有很多了,用它來作爲hot fix的關鍵代碼如下:

其中extraElements指向的是patch中的dex,original指向的是bug.apk中的dex,combined是這兩者合併之後的dex列表。


上面的代碼來記DynamicAPK,可見host fix的原理其實就是將補丁文件中的dex文件放到apk的前面。而做Plugin開發時,就是將dex放到apk的後面。

爲什麼這樣做可以了呢?

來看看系統中DexClassLoader加載class的代碼實現:

從這段代碼可以看出,系統在加載class的時候會依次遍歷dex列表,找到第一個滿足的class就直接return回去了。

所以如果將補丁中的dex放到APK的前面,系統就會先加載補丁文件中的class文件,從而達到了在內存中替換掉APK class的效果,以此來fix bug。


MultiDex需要注意的:

默認class文件只能引用同一個dex中的class文件。因爲在加載的時候如果檢測到class沒有引用其他dex中的class,就會加上CLASS_ISPREVERIFIED標誌。

而在java源文件中,是不能直接引用dex文件的。

所以要防止被打上CLASS_ISPREVERIFIED,就只有在編譯完成後修改class文件,讓它引用其他dex中的文件。

具體參考http://zhuanlan.zhihu.com/magilu/20308548


攜程的DynamicAPK說的支持hot fix,應該只是指加載dex的時候,可以將補丁中的dex放到apk的前面。

但關於如果製作補丁,如何往class中插入代碼都沒有,這纔是hot fix的真正大頭。

這樣就說支持真的好麼.........

話說回來既能Plugin開發,又能host fix,這樣的框架應該纔是理想的.....


基於上述的調研,最後將目標鎖定在了Nuwa上。


下面是將Nuwa整合進我的project:

1.下載源碼,編譯成功。將Nuwa/nuwa/build/outputs/aar目錄下的nuwa.jar放到我的libs目錄下:

還要添加lib依賴哦:

dependencies {
...................
     compile files('libs/nuwa.jar')
....................
 }


2.在top level的build.gradle中添加gradle依賴:


3.在我的App module的build.gradle中使用nuwa 插件,添加如下代碼:




需要注意的:

   我使用了provider,它會比Application先起來,所以需要使用excludeClass來將這些在Application中之前起來的class全部排除掉。

   也就是編譯後不進行class注入,當然這樣這幾個文件也就無法進行hot fix了。不過這些很少的文件一般也不會動吧。

  否則就會發生class not found(因爲第二個dex此時都還沒有加載,而每個class中都有被插入使用第二個dex class的代碼)。

E/AndroidRuntime(11948): java.lang.NoClassDefFoundError: cn.jiajixin.nuwa.Hack
E/AndroidRuntime(11948): 	at com.nq.mam.app.MAMApp$1.<init>(MAMApp.java:201)
E/AndroidRuntime(11948): 	at com.nq.mam.app.MAMApp.<init>(MAMApp.java:201)
E/AndroidRuntime(11948): 	at java.lang.Class.newInstanceImpl(Native Method)
E/AndroidRuntime(11948): 	at java.lang.Class.newInstance(Class.java:1208)
E/AndroidRuntime(11948): 	at android.app.Instrumentation.newApplication(Instrumentation.java:990)
E/AndroidRuntime(11948): 	at android.app.Instrumentation.newApplication(Instrumentation.java:975)
E/AndroidRuntime(11948): 	at android.app.LoadedApk.makeApplication(LoadedApk.java:504)
E/AndroidRuntime(11948): 	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4367)
E/AndroidRuntime(11948): 	at android.app.ActivityThread.access$1600(ActivityThread.java:141)
E/AndroidRuntime(11948): 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1273)
E/AndroidRuntime(11948): 	at android.os.Handler.dispatchMessage(Handler.java:102)
E/AndroidRuntime(11948): 	at android.os.Looper.loop(Looper.java:136)
E/AndroidRuntime(11948): 	at android.app.ActivityThread.main(ActivityThread.java:5072)
E/AndroidRuntime(11948): 	at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(11948): 	at java.lang.reflect.Method.invoke(Method.java:515)
E/AndroidRuntime(11948): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
E/AndroidRuntime(11948): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:609)
E/AndroidRuntime(11948): 	at dalvik.system.NativeStart.main(Native Method)
W/ActivityManager( 1218):   Force finishing activity com.nq.mdm/.activity.MDMSplashActivity


以上基本就是配置的全部過程了,還是比較簡單。

編譯出APP後,記得保存APP下build/outputs/nuwa目錄哦,這裏面記錄了每個class文件的hash值。

另外建議製作補丁時,還是用和原來編譯APP同樣的環境,免得生成的hash有什麼不對的。。

在製作補丁文件時會,會將新的APK中的class的hash與該文件比較,將不一樣的class文件提取出來製作成補丁文件。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章