HotFix介绍及HotFix开源项目推荐

What is HotFix?

以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装(来自:安卓App热补丁动态修复技术介绍)

HotFix原理

首先HotFix原理是基于Android Dex分包方案的,而Dex分包方案的关键就是Android的ClassLoader体系。ClassLoader的继承关系如下:


ClassLoader继承关系


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

  • 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,现在很清楚了,实现热修复,我们需要完成两个任务:

  1. 改变BaseDexClassLoader中的dexElements数组,将我们的patch.jar插入到dexElements数组的第一个位置。
  2. 在打包的时候,我们要阻止类被打上CLASS_ISPREVERIFIED标记

HotFix框架汇总

RoccoFix使用问题

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

参考

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



文/zyyoona7(简书作者)
原文链接:http://www.jianshu.com/p/6f0ae1e364d9
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值