android热修复原理总结

来源:互联网 发布:慕尼黑协定 知乎 编辑:程序博客网 时间:2024/05/17 06:40

背景

当app发布之后如果出现了紧急的线上bug,整个公司都会为此忙的焦头烂额,现公司如果线上出现严重的P1级bug,甚至大半夜整个项目组都得来紧急修复上线,而bug的原因可能仅仅是传错了参数,或者写错一行代码,而且修复后的app又得重新上架,直到用户更新后bug才会被修正。那热修复技术的出现就能很大程度上缓解这种情况,修复后不需要重新上架,用户也不需要重新下载安装。

原理

github上的热修复框架如nuwa,HotFix原理都是依据安卓App热补丁动态修复技术介绍和Android dex分包方案
这两篇文章,我这里也只是对这两篇文章做一个自己的总结加深理解。
关于nuwa框架的使用看另一篇博客热修复框架nuwa的使用
热修复原理是基于Android的分包方案的,那么什么是Android的分包方案呢,Android2.3之前执行dexopt的内存只有5M,每个dex的方法数不能超过65535,当app功能复杂,类和方法特别多的时候就会在编译时报错。Android dex分包方案中的描述:

当一个app的功能越来越复杂,代码量越来越多,也许有一天便会突然遇到下列现象:

  1. 生成的apk在2.3以前的机器无法安装,提示INSTALL_FAILED_DEXOPT

  2. 方法数量过多,编译时出错,提示:

Conversion to Dalvik format failed:Unable to execute dex: method ID
not in [0, 0xffff]: 65536

出现这种问题的原因是:

  1. Android2.3及以前版本用来执行dexopt(用于优化dex文件)的内存只分配了5M

  2. 一个dex文件最多只支持65536个方法。

解决办法是将编译好的class文件打成两个dex的包,运行时注入ClassLoader。如何注入呢,先看一下Android的ClassLoader体系,图片是分包方案里扒的。
这里写图片描述
可以看到实现类有两个DexClassLoader和PathClassLoader,其中PathClassLoader是用来Android用来加载Android系统类和应用的加载器,DexClassLoader用来加载.dex和.jar中的class.dex文件。看一下BaseDexClassLoader加载类的方法,从pathList里根据类名找,找不到就class not found。pathList是BaseDexClassLoader中的一个对象,它包含一个dexElements集合,找类就是听过遍历这个集合,拿到dexFile去找类。

一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。(来自:安卓App热补丁动态修复技术介绍)

#BaseDexClassLoader@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {    Class clazz = pathList.findClass(name);    if (clazz == null) {        throw new ClassNotFoundException(name);    }    return clazz;}#DexPathListpublic Class findClass(String name) {    for (Element element : dexElements) {        DexFile dex = element.dexFile;        if (dex != null) {            Class clazz = dex.loadClassBinaryName(name, definingContext);            if (clazz != null) {                return clazz;            }        }    }    return null;}#DexFilepublic Class loadClassBinaryName(String name, ClassLoader loader) {    return defineClass(name, loader, mCookie);}private native static Class defineClass(String name, ClassLoader loader, int cookie);

这样我们将有bug的类打成patch.jar然后插入到dexElements的最前边位置,findclass的时候就会从我们的patch.jar 中开始找,找到类之后就返回,有问题的类就被补丁包中的替换掉了。之后还有一个CLASS_ISPREVERIFIED的问题,详细可以看安卓App热补丁动态修复技术介绍主要是说,如果类和其引用类如果不在同一个dex包里就会报错,校检出这个错的前提是引用类被打上了CLASS_ISPREVERIFIED标识,这个标识是什么时候被打上的呢,在虚拟机启动的时候如果一个类的private,static,或者构造方法直接引用到的类都在同一个dex包里就会给当前类打上这个标识,所以要阻止这个表示被打上,我们要让类引用一个外部dex包里的类,往所有的类的构造函数中插入一段

if (ClassVerifier.PREVENT_VERIFY) {    System.out.println(AntilazyLoad.class);}public class AntilazyLoad{}

所以我们还需要一个hack.dex,写个空的类AntilazyLoad打成dex包,而且必须先加载这个包,否则引用这个类的地方就会class not fount,加载方法就和之前的一样啦,要注意

Application作为应用的入口不能插入这段代码。(因为载入hack.dex的代码是在Application中onCreate中执行的,如果在Application的构造函数里面插入了这段代码,那么就是在hack.dex加载之前就使用该类,该类一次找不到,会被永远的打上找不到的标志)(来自:安卓App热补丁动态修复技术介绍)。

1 0