一、技术背景
1.传统开发流程
从流程来看,传统的开发流程存在很多弊端:
重新发布版本代价太大
用户下载安装成本太高
BUG修复不及时,用户体验太差
2.热修复开发流程
而热修复的开发流程显得更加灵活,优势很多:
无需重新发版,实时高效热修复
用户无感知修复,无需下载新的应用,代价小
修复成功率高,把损失降到最低
二、业界热门的热修复技术
热修复作为当下热门的技术,在业界内比较著名的有腾讯QQ空间的超级补丁、微信的Tinker以及阿里百川的HotFix。
1.QQ空间超级补丁
超级补丁技术基于DEX分包方案,使用了多DEX加载的原理,大致的过程就是:把BUG方法修复以后,放到一个单独的DEX里,插入到dexElements数组的最前面,让虚拟机去加载修复完后的方法。
2.微信Tinker
微信针对QQ空间超级补丁技术的不足提出了一个提供DEX差量包,整体替换DEX的方案。主要的原理是与QQ空间超级补丁技术基本相同,区别在于不再将 patch.dex增加到elements数组中,而是差量的方式(DexDiff)给出patch.dex,然后将patch.dex与应用的 classes.dex合并,然后整体替换掉旧的DEX文件,以达到修复的目的。
3.阿里百川的HotFix
阿里百川推出的热修复HotFix服务,通过增加或替换整个DEX的方案,提供了一种运行时在Native修改Filed指针的方式,实现方法的替换,达到即时生效无需重启,对应用无性能消耗的目的。
以上3种技术各有优缺点,关于这3种技术实现原理的简要分析可以参考网络。
下面详细介绍QQ空间超级补丁的实现原理。
三、QQ空间超级补丁的原理
超级补丁方案是基于android MultiDex方案的,关于dex分包方案,网上有几篇解释,这里不再赘述。简单概括一下,就是把多个dex文件塞入到app的ClassLoader 中,但是android dex拆包方案中的类是没有重复的,如果classes.dex和classes1.dex中有重复的类,当用到这个重复的类时,系统会选择哪个类进行加载呢?
要搞明白这个问题,我们需要从Android的ClassLoader体系谈起。
Android中加载类一般使用的是PathClassLoader和DexClassLoader,首先看下这两个类的区别:
对于PathClassLoader,从文档的注释上来看:
PathClassLoader
Provides a simple {@link ClassLoader} implementation that operates on a list of files and directories in the local file system, but does not attempt to load classes from the network. Android uses this class for its system class loader and for its application class loader(s).
可以看出,Android是使用这个类作为其系统类和应用类的加载器。并且对于这个类呢,只能去加载已经安装到Android系统中的apk文件。
再来看一下DexClassLoader的文档注释:
DexClassLoader
A class loader that loads classes from {@code .jar} and {@code .apk} files containing a {@code classes.dex} entry. This can be used to execute code not installed as part of an application.
可以看出,该类呢,可以用来从.jar和.apk类型的文件内部加载classes.dex文件。可以用来执行非安装的程序代码。
对于加载类,无非就是给个className,然后去findClass。PathClassLoader和DexClassLoader都继承自BaseDexClassLoader。在BaseDexClassLoader中有如下源码:
#BaseDexClassLoader @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = pathList.findClass(name); if (clazz == null) { throw new ClassNotFoundException(name); } return clazz; } #DexPathList public Class findClass(String name) { for (Element element : dexElements) { // 每个element就是一个Dex文件 DexFile dex = element.dexFile; if (dex != null) { Class clazz = dex.loadClassBinaryName(name, definingContext); if (clazz != null) { return clazz; } } } return null; } #DexFile public Class loadClassBinaryName(String name, ClassLoader loader) { return defineClass(name, loader, mCookie); } private native static Class defineClass(String name, ClassLoader loader, int cookie);
一个CLassLoader 可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历 dexElements,然后从当前的dex文件中找类,如果找到则返回,如果找不到则从下一个dex文件中继续查找。
理论上,如果在不同的dex中有相同的类存在,那么会优先选择排在前面的dex文件中的类:
热补丁方案便是在此基础上产生的,把有问题的类打包到一个dex(patch.dex)中去,然后把这个dex插入到dexElements的最前面:
关于拆分dex的原理,如果没有相关项目的话,可以参考一下谷歌的multidex方案实现。然后在插入数组的时候,把补丁包插入到最前面去。
OK,好像问题很简单,轻松的搞定了,但是当你按照上面的思路准备好了patch.dex,当加载类的时候出现了java.lang.IllegaAccessError:Class ref in pre-verified class resoved to unexpected implementation异常。
为什么会出现以上问题呢?
让我们来搜索一下抛出错误的代码所在,定位到Android源码中的Resolve.cpp中的dvmResolveClass方法,可以看到只要满足最外层(!fromUnverifiedConstant && IS_CLASS_FLAG_SET(referrer, CLASS_ISPREVERIFIED))的条件,就会抛出以上异常。Qzone就是从CLASS_ISPREVERIFIED标记入手,想办法让Class不打上该标签。
共同學(xué)習(xí),寫下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章