Android HotFix

来源:互联网 发布:ubuntu 重置root密码 编辑:程序博客网 时间:2024/05/04 00:32

What is HotFix?

以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装(来自:安卓App热补丁动态修复技术介绍)
HotFix原理
首先HotFix原理是基于Android Dex分包方案的,而Dex分包方案的关键就是Android的ClassLoader体系。ClassLoader的继承关系如下:

ClassLoader继承关系

这里我们可以用的是PathClassLoader和DexClassLoader,接下来看看这两个类的注释:

PatchClassLoader/*** 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).*/

这个类被用作系统类加载器和应用类(已安装的应用)加载器。

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文件中加载classed.dex,可以用来执行没有安装的程序代码。
通过上面的两个注释可以清楚这两个类的作用了,很显然我们要用的是DexClassLoader,对插件化了解的小伙伴们对这个类肯定不会陌生的,对插件化不了解的也没关系。下面会更详细的介绍。
我们知道了PathClassLoader和DexClassLoader的应用场景,接下来看一下是如何加载类的,看上面的继承关系这里两个类都是继承自BaseDexClassLoader,所以查找类的方法也在BaseDexClassLoader中,下面是部分源码:

/** * Base class for common functionality between various dex-based * {@link ClassLoader} implementations. */public class BaseDexClassLoader extends ClassLoader {     /** structured lists of path elements */     private final DexPathList pathList;     //...some code     @Override     protected Class<?> findClass(String name) throws ClassNotFoundException {         Class clazz = pathList.findClass(name);         if (clazz == null) {             throw new ClassNotFoundException(name);         }         return clazz;     }    //...some code}

可以看到在findClass()方法中用到了pathList.findClass(name),而pathList的类型是DexPathList,下面看一下DexPathList的findClass()方法源码:

/** * A pair of lists of entries, associated with a {@code ClassLoader}. * One of the lists is a dex/resource path — typically referred * to as a "class path"list, and the other names directories * containing native code libraries. Class path entries may be any of: * a {@code .jar} or {@code .zip} file containing an optional * top-level {@code classes.dex} file as well as arbitrary resources, * or a plain {@code .dex} file (with no possibility of associated * resources). * * <p>This class also contains methods to use these lists to look up * classes and resources.</p> *//*package*/ final class DexPathList {     /** list of dex/resource (class path) elements */     private final Element[] dexElements;    /**     * Finds the named class in one of the dex files pointed at by     * this instance. This will find the one in the earliest listed     * path element. If the class is found but has not yet been     * defined, then this method will define it in the defining     * context that this instance was constructed with.     *     * @return the named class or {@code null} if the class is not     * found in any of the dex files     */     public 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;     }}

这个方法里面有调用了dex.loadClassBinaryName(name, definingContext),然后我们来看一下DexFile的这个方法:

/** * Manipulates DEX files. The class is similar in principle to * {@link java.util.zip.ZipFile}. It is used primarily by class loaders. * <p> * Note we don't directly open and read the DEX file here. They're memory-mapped * read-only by the VM. */public final class DexFile {    /**     * See {@link #loadClass(String, ClassLoader)}.     *     * This takes a "binary" class name to better match ClassLoader semantics.     *     * @hide     */     public Class loadClassBinaryName(String name, ClassLoader loader){         return defineClass(name, loader, mCookie);     }     private native static Class defineClass(String name, ClassLoader loader, int cookie);}

好了,关联的代码全部贴上了,理解起来并不难,总结一下流程:BaseDexClassLoader中有一个DexPathList对象pathList,pathList中有个Element数组dexElements(Element是DexPathList的静态内部类,在Element中会保存DexFile的对象),然后遍历Element数组,通过DexFile对象去查找类。
更通俗的说:

一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。(出自安卓App热补丁动态修复技术介绍)
so,通过上面介绍,我们可以将patch.jar(补丁包),放在dexElements数组的第一个元素,这样优先找到我们patch.jar中的新类去替换之前存在bug的类。
方案有了,但是我们还差一个步骤,就是防止类被打上CLASS_ISPREVERIFIED的标记
解释一下:在apk安装的时候,虚拟机会将dex优化成odex后才拿去执行。在这个过程中会对所有class一个校验。校验方式:假设A该类在它的static方法,private方法,构造函数,override方法中直接引用到B类。如果A类和B类在同一个dex中,那么A类就会被打上CLASS_ISPREVERIFIED标记。A类如果还引用了一个C类,而C类在其他dex中,那么A类并不会被打上标记。换句话说,只要在static方法,构造方法,private方法,override方法中直接引用了其他dex中的类,那么这个类就不会被打上CLASS_ISPREVERIFIED标记。(引用自Android热补丁动态修复技术(二):实战!CLASS_ISPREVERIFIED问题!)
O..O..OK,现在很清楚了,实现热修复,我们需要完成两个任务:
改变BaseDexClassLoader中的dexElements数组,将我们的patch.jar插入到dexElements数组的第一个位置。
在打包的时候,我们要阻止类被打上CLASS_ISPREVERIFIED标记

HotFix框架汇总
RocooFix(推荐)
Nuwa
HotFix
AndFix
Tinker_imitator(微信热更新方案实践)

RoccoFix使用问题
RocooFix的正确教程,不生成patch.jar请按这个步骤来执行
待更新

参考
安卓App热补丁动态修复技术介绍
Android dex分包方案
Android 热补丁动态修复框架小结
Android热补丁动态修复技术(一):从Dex分包原理到热补丁
Android热补丁动态修复技术(二):实战!CLASS_ISPREVERIFIED问题!
Android热补丁动态修复技术(三)—— 使用Javassist注入字节码,完成热补丁框架雏形(可使用)
Android热补丁动态修复技术(四):自动化生成补丁——解决混淆问题

文/zyyoona7(简书作者)
原文链接:http://www.jianshu.com/p/6f0ae1e364d9
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

0 0
原创粉丝点击