版权声明:本文由johncz原创文章,转载请注明出处:
文章原文连接:https://www.qcloud.com/community/article/169java
来源:腾云阁 https://www.qcloud.com/communityandroid
当一个App发布以后,忽然发现了一个严重bug须要进行紧急修复,这时候公司各方就会忙得焦头烂额:从新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。有时候仅仅是为了修改了一行代码,也要付出巨大的成本进行换包和从新发布。
这时候就提出一个问题:有没有办法以补丁的方式动态修复紧急Bug,再也不须要从新发布App,再也不须要用户从新下载,覆盖安装?
虽然Android系统并无提供这个技术,可是很幸运的告诉你们,答案是:能够,咱们QQ空间提出了热补丁动态修复技术来解决以上这些问题。数组
空间Android独立版5.2发布后,收到用户反馈,结合版没法跳转到独立版的访客界面,天天都较大的反馈。在之前只能紧急换包,从新发布。成本很是高,也影响用户的口碑。最终决定使用热补丁动态修复技术,向用户下发Patch,在用户无感知的状况下,修复了外网问题,取得很是好的效果。缓存
该方案基于的是android dex分包方案的,关于dex分包方案,网上有几篇解释了,因此这里就再也不赘述,具体能够看这里
简单的归纳一下,就是把多个dex文件塞入到app的classloader之中,可是android dex拆包方案中的类是没有重复的,若是classes.dex和classes1.dex中有重复的类,当用到这个重复的类的时候,系统会选择哪一个类进行加载呢?
让咱们来看看类加载的代码:
一个ClassLoader能够包含多个dex文件,每一个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,而后从当前遍历的dex文件中找类,若是找类则返回,若是找不到从下一个dex文件继续查找。
理论上,若是在不一样的dex中有相同的类存在,那么会优先选择排在前面的dex文件的类,以下图:
在此基础上,咱们构想了热补丁的方案,把有问题的类打包到一个dex(patch.dex)中去,而后把这个dex插入到Elements的最前面,以下图:
好,该方案基于第二个拆分dex的方案,方案实现若是懂拆分dex的原理的话,你们应该很快就会实现该方案,若是没有拆分dex的项目的话,能够参考一下谷歌的multidex方案实现。而后在插入数组的时候,把补丁包插入到最前面去。
好,看似问题很简单,轻松的搞定了,让咱们来试验一下,修改某个类,而后打包成dex,插入到classloader,当加载类的时候出现了(本例中是QzoneActivityManager要被替换):
为何会出现以上问题呢?
从log的意思上来说,ModuleManager引用了QzoneActivityManager,可是发现这这两个类所在的dex不在一块儿,其中:app
让咱们搜索一下抛出错误的代码所在,嘿咻嘿咻,找到了一下代码:
从代码上来看,若是两个相关联的类在不一样的dex中就会报错,可是拆分dex没有报错这是为何,原来这个校验的前提是:
ide
若是引用者(也就是ModuleManager)这个类被打上了CLASS_ISPREVERIFIED标志,那么就会进行dex的校验。那么这个标志是何时被打上去的?让咱们在继续搜索一下代码,嘿咻嘿咻~,在DexPrepare.cpp找到了一下代码:
这段代码是dex转化成odex(dexopt)的代码中的一段,咱们知道当一个apk在安装的时候,apk中的classes.dex会被虚拟机(dexopt)优化成odex文件,而后才会拿去执行。
虚拟机在启动的时候,会有许多的启动参数,其中一项就是verify选项,当verify选项被打开的时候,上面doVerify变量为true,那么就会执行dvmVerifyClass进行类的校验,若是dvmVerifyClass校验类成功,那么这个类会被打上CLASS_ISPREVERIFIED的标志,那么具体的校验过程是什么样子的呢?
此代码在DexVerify.cpp中,以下:
函数
归纳一下就是若是以上方法中直接引用到的类(第一层级关系,不会进行递归搜索)和clazz都在同一个dex中的话,那么这个类就会被打上CLASS_ISPREVERIFIED:
因此为了实现补丁方案,因此必须从这些方法中入手,防止类被打上CLASS_ISPREVERIFIED标志。
最终空间的方案是往全部类的构造函数里面插入了一段代码,代码以下:
if (ClassVerifier.PREVENT_VERIFY) { System.out.println(AntilazyLoad.class); }
性能
其中AntilazyLoad类会被打包成单独的hack.dex,这样当安装apk的时候,classes.dex内的类都会引用一个在不相同dex中的AntilazyLoad类,这样就防止了类被打上CLASS_ISPREVERIFIED的标志了,只要没被打上这个标志的类均可以进行打补丁操做。
而后在应用启动的时候加载进来.AntilazyLoad类所在的dex包必须被先加载进来,否则AntilazyLoad类会被标记为不存在,即便后续加载了hack.dex包,那么他也是不存在的,这样屏幕就会出现茫茫多的类AntilazyLoad找不到的log。
因此Application做为应用的入口不能插入这段代码。(由于载入hack.dex的代码是在Application中onCreate中执行的,若是在Application的构造函数里面插入了这段代码,那么就是在hack.dex加载以前就使用该类,该类一次找不到,会被永远的打上找不到的标志)
其中:
之因此选择构造函数是由于他不增长方法数,一个类即便没有显式的构造函数,也会有一个隐式的默认构造函数。
空间使用的是在字节码插入代码,而不是源代码插入,使用的是javaassist库来进行字节码插入的。
隐患:
虚拟机在安装期间为类打上CLASS_ISPREVERIFIED标志是为了提升性能的,咱们强制防止类被打上标志是否会影响性能?这里咱们会作一下更加详细的性能测试.可是在大项目中拆分dex的问题已经比较严重,不少类都没有被打上这个标志。
如何打包补丁包:测试