现在,热修复的具体实现方案开源的也有很多,原理也大同小异,本篇文章以Nuwa为例,深入剖析。
Nuwa的github地址
https://github.com/jasonross/Nuwa
以及用于hotpatch生成的gradle插件地址
https://github.com/jasonross/NuwaGradle
而Nuwa的具体实现是根据QQ空间的热修复方案来实现的。安卓App热补丁动态修复技术介绍。在阅读本篇文章之前,请先阅读该文章。
从QQ空间终端开发团队的文章中可以总结出要进行热更新只需要满足下面两点就可以了:
- 动态加载补丁dex,并将补丁dex插入到dexElements最前面
- 要实现热更新,需要热更新的类要防止被打上ISPREVERIFIED标记,关于这个标记,请阅读上面QQ空间团队的文章。
对于第一点,实现很简单,通过DexClassLoader对象,将补丁dex对象加载进来,再通过反射将补丁dex插入到dexElements最前面即可。具体可参考谷歌的Multidex的实现。
而对于第二点,关键就是如何防止类被打上ISPREVERIFIED这个标记。
简单来说,就是将所有类的构造函数中,引用另一个hack.dex中的类,这个类叫Hack.class,然后在加载补丁patch.dex前动态加载这个hack.dex,但是有一个类的构造函数中不能引用Hack.class,这个类就是Application类的子类,一旦这个类的构造函数中加入Hack.class这个类,那么程序运行时就会找不到Hack.class这个类,因为还没有被加载。也就是说,一个类直接引用到的类不在同一个dex中即可。这样,就能防止类被打上ISPREVERIFIED标记并能进行热更新。
我们先来看Nuwa的实现,再去看Nuwa的插件的实现。
使用Nuwa的时候需要在attachBaseContext方法中初始化
1 2 3 4 5 |
@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); Nuwa.init(this); } |
Nuwa预先将Hack.class这个类(空实现)打成apk文件,放在asserts目录中,在init方法中,做的就是将asserts目录中的这个文件拷贝到文件目录下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public static void init(Context context) { File dexDir = new File(context.getFilesDir(), DEX_DIR); dexDir.mkdir(); String dexPath = null; try { dexPath = AssetUtils.copyAsset(context, HACK_DEX, dexDir); } catch (IOException e) { Log.e(TAG, "copy " + HACK_DEX + " failed"); e.printStackTrace(); } loadPatch(context, dexPath); } |
首先创建文件目录将asserts目录下的hack.apk拷到该目录,然后调用loadPatch方法将该apk动态加载进来。loadPatch方法也是之后进行热修复的关键方法,你的所有补丁文件都是通过这个方法动态加载进来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public static void loadPatch(Context context, String dexPath) { if (context == null) { Log.e(TAG, "context is null"); return; } if (!new File(dexPath).exists()) { Log.e(TAG, dexPath + " is null"); return; } File dexOptDir = new File(context.getFilesDir(), DEX_OPT_DIR); dexOptDir.mkdir(); try { DexUtils.injectDexAtFirst(dexPath, dexOptDir.getAbsolutePath()); } catch (Exception e) { Log.e(TAG, "inject " + dexPath + " failed"); e.printStackTrace(); } } |
loadPatch方法中主要是调用DexUtils.injectDexAtFirst()方法将dex插入到dexElements最前面。该方法如下。
1 2 3 4 5 6 7 8 |
public static void injectDexAtFirst(String dexPath, String defaultDexOptPath) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException { DexClassLoader dexClassLoader = new DexClassLoader(dexPath, defaultDexOptPath, dexPath, getPathClassLoader()); Object baseDexElements =assLoader()); Object baseDexElements =adle">https://github.com/jasonross/NuwaGradle
而Nuwa的具体实现是根据QQ空间的热修复方案来实现的。安卓App热补丁动态修复技术介绍。在阅读本篇文章之前,请先阅读该文章。 从QQ空间终端开发团队的文章中可以总结出要进行热更新只需要满足下面两点就可以了:
对于第一点,实现很简单,通过DexClassLoader对象,将补丁dex对象加载进来,再通过反射将补丁dex插入到dexElements最前面即可。具体可参考谷歌的Multidex的实现。 而对于第二点,关键就是如何防止类被打上ISPREVERIFIED这个标记。 简单来说,就是将所有类的构造函数中,引用另一个hack.dex中的类,这个类叫Hack.class,然后在加载补丁patch.dex前动态加载这个hack.dex,但是有一个类的构造函数中不能引用Hack.class,这个类就是Application类的子类,一旦这个类的构造函数中加入Hack.class这个类,那么程序运行时就会找不到Hack.class这个类,因为还没有被加载。也就是说,一个类直接引用到的类不在同一个dex中即可。这样,就能防止类被打上ISPREVERIFIED标记并能进行热更新。 我们先来看Nuwa的实现,再去看Nuwa的插件的实现。 使用Nuwa的时候需要在attachBaseContext方法中初始化
Nuwa预先将Hack.class这个类(空实现)打成apk文件,放在asserts目录中,在init方法中,做的就是将asserts目录中的这个文件拷贝到文件目录下。
首先创建文件目录将asserts目录下的hack.apk拷到该目录,然后调用loadPatch方法将该apk动态加载进来。loadPatch方法也是之后进行热修复的关键方法,你的所有补丁文件都是通过这个方法动态加载进来。
loadPatch方法中主要是调用DexUtils.injectDexAtFirst()方法将dex插入到dexElements最前面。该方法如下。
|