通过上一篇的分析,要进行在线修复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文件提取出来制作成补丁文件。