热修复初探

热修复这个技术点最近有点火,有QQ空间开发团队为其背书,还有的大厂开源的热修复框架,这些对于推进这项技术也起了很大的做用。做为一个有追求的工程师菜鸟,今天,我想经过几篇文章把这种在线修复的解决思路以及几种具体的实现方案理一遍。java

在开始分析以前,首先须要说说热修复解决了一个什么问题,闭上眼睛,想象一个场景:当你的产品刚上线,发现有一个致使闪退的空指针异常的问题或者程序重大漏洞急需解决,那么问题来了,怎么修复?常规的,固然是修改完有问题的方法,而后赶忙再打包,推送到用户强制更新,可是,如今还有另外一种方式,就是程序在线下载修复文件到本地,而后用修改过的类覆盖原来有问题的类,这样的用户体验是一颗赛艇的。android

好了,回到热修复这个话题。网络

首先我想先谈谈热修复自己,热修复是一种动态修复程序解决问题的思想,其自己是有不少不一样的具体实现方案的,阿里的基于C/C++层操控method指针的Dexposed,AndFix,以及QQ空间的基于dex分包的HotFix,后者和前者的热修复方案在原理上大相径庭,能够说各有千秋。而我在查阅资料的时候,发现不少Blog都不够严谨,每每标题声称热修复技术可是只解释QQ空间的解决方案,能够说这种作法是容易误导人的,虽然不能算错误,可是不太严谨。框架

目前的热修复技术的解决方案有不少,我想就上面提到的两种解决方案来作详细的探讨。ide

1,基于C层指针替换的Dexposed和AndFix

  • 这两个热修复的框架,在底层原理上是基本一致的,因此我想把他们放在一块儿探讨,

他们都作了大体三件事:优化

  • 1,在C/C++层将Java层中出问题的方法修改成native方法
  • 2,获取问题方法call到C层的指针
  • 3,经过获取的指针作相应的操做:调用Java层的回调方法继续处理(DexPosed)或者直接经过反射调用Java层的补丁方法(AndFix)。

以Dexposed为例:spa

dexposed.jpg

至于具体的代码解释,请直接看Android中免Root实现Hook的Dexposed框架实现原理解析以及如何实现应用的热修复3d

这两种热修复框架的区别在于:指针

  • Dexposed暂时不支持ART模式,AndFix支持
  • AndFix方案更加成熟,更加自动化(毕竟是支付宝出的)

2,基于Dex分包的HotFix

这个解决方案很巧妙,基于Google推出的的Multidex方案,以ClassLoader的方式完成对问题类的替换。code

因此这个问题必定会先谈Android的分包方案:为了解决Android4.x系统中65536的方法数限制,Android推出Multidex方案,将一个完整的APK中的Dex拆分红好几个dex,经过PathClassLoader 这个加载器来加载。

当点开程序的时候,PathClassLoader 会把分包的多个dex添加到父类中的一个DexPathList 中

DexPathList 详情以下:

public class BaseDexClassLoader extends ClassLoader {

    private final DexPathList pathList;
}复制代码
/*package*/ final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private static final String JAR_SUFFIX = ".jar";
    private static final String ZIP_SUFFIX = ".zip";
    private static final String APK_SUFFIX = ".apk";

    /** class definition context */
    private final ClassLoader definingContext;

    /** list of dex/resource (class path) elements 也就是dex列表咯*/
    private final Element[] dexElements;

    /** list of native library directory elements */
    private final File[] nativeLibraryDirectories;复制代码

那么当须要加载某个类的时候,是怎么加载的呢?

//BaseDexClassLoader: 
    @Override  
    protected Class< ?> findClass(String name) throws ClassNotFoundException {  
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); 
        Class c = pathList.findClass(name, suppressedExceptions);  
        if (c == null) {  
            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);  
            for (Throwable t : suppressedExceptions) {  
                cnfe.addSuppressed(t);  
           }  
        throw cnfe;  
        }  
        return c;  
    }复制代码

findClass()方法以下:

public Class findClass(String name, List<Throwable> suppressed) {      
         for (Element element : dexElements) {  
           DexFile dex = element.dexFile;  
            if (dex != null) {  
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);  
                if (clazz != null) {  
                    return clazz;  
                }  
            }  
       }  
        if (dexElementsSuppressedExceptions != null) {  
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));  
        }  
        return null;  
    }复制代码

如你所见,当须要加载一个类的时候,会在pathList中去寻找,而且是经过顺序遍历各个dex包的方式,一旦找到目标类,则中止遍历

qqZone.png

这就给了咱们一个想法,有没有可能把打了补丁的dex插入到pathList中,当须要加载有问题的类的时候,根据遍历,首先查到已经修复的类,遍历结束,也就完成了修复。(固然了,这个想法是腾讯空间Android工程师想到的)

有了想法,也得有合适的加载器啊。结果你猜怎么着?Android还真提供了这样的机会。

在Android中也有三个类加载器,分别是UrlClassLoader,PathClassLoader,DexClassLoader.

  • UrlClassLoader 从Url列表中加载相关的jar文件,可是dalvik没法直接识别jar,so.....
  • PathClassLoader 它只会去读取 /data/dalvik-cache 目录下的 dex 文件,就是已安装的apk,
  • DexClassLoader 能够用来从.jar和.apk类型的文件内部加载classes、dex文件。并且,它和PathClassLoader继承自共同的父类。显然,这是最合适的加载器。

android.png

好了,基本机制到这里就结束了,还有一些问题却没有被提出来,不过网络上已经有了很好的解决方案了。

  • 如何防止本身的类被打上 CLASS_ISPREVERIFIED标志
    • 这个标志是虚拟机的一种优化手段,打上这个标志以后,就不会引用其余dex中的类,若是引用了,则报错。解决方案也很简单,就是在类中引用其余dex包的引用,具体方法请直接Google。

虽然如今咱们公司的开发团队确定用不上热修复技术,可是做为工程师却必须对新技术有所研究。近期我会继续研究热修复 HotFix 框架的源码,有必要的话会对DexClassLoader如何动态的插入jar包或者dex文件给出更详细的解析,暂时没有时间解释了,你们先上车

卧槽 说错话了.....

相关文章
相关标签/搜索