热修复

热修复流程
  1. 线上检测到严重crash
  2. 拉去bugfix分支并在分支上修复问题
  3. jenkins构建和补丁生成
  4. app通过推送或主动拉去补丁文件
  5. 将bugfix代码合到dev、master上
主流热更新框架介绍
    1. DexPosed aop框架
    1. AndFix
    1. SopFix
    1. Nuwa
热修复两大类
  • ClassLoader 加载方案
  • Native层替换方案
类加载原理

说起热修复就不得不提类的加载机制,和常规的JVM类似,在Android中类的加载也是通过ClassLoader来完成,具体来说就是PathClassLoader 和 DexClassLoader 这两个Android专用的类加载器,这两个类的区别如下:

  • PathClassLoader:只能加载已经安装到Android系统中的apk文件(/data/app目录),是Android默认使用的类加载器。
  • DexClassLoader:可以加载任意目录下的dex/jar/apk/zip文件,也就是我们一开始提到的补丁。

这两个类都是继承自BaseDexClassLoader

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

DexPathList的构造函数,就是将参数中传递进来的程序文件(就是补丁文件)封装成Element对象,并将这些对象添加到一个Element的数组集合dexElements中去。

双亲委托模式

类加载器查找Class所采用的是双亲委托模式,所谓双亲委托模式就是首先判断该Class是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次的进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找。
这样讲可能会有些抽象,来看下面的图。


image.png

我们知道类加载子系统用来查找和加载Class文件到 Java 虚拟机中,假设我们要加载一个位于D盘的Class文件,这时系统所提供的类加载器不能满足条件,这时就需要我们自定义类加载器继承自java.lang.ClassLoader,并复写它的findClass方法。加载D盘的Class文件步骤如下:

自定义类加载器首先从缓存中要查找Class文件是否已经加载,如果已经加载就返回该Class,如果没加载则委托给父加载器也就是App ClassLoader。
按照上图中红色虚线的方向递归步骤1。
一直委托到Bootstrap ClassLoader,如果Bootstrap ClassLoader在缓存中还没有查找到Class文件,则在自己的规定路径JAVA_HOME/jre/libr中或者-Xbootclasspath选项指定路径的jar包中进行查找,如果找到则返回该Class,如果没有则交给子加载器Extensions ClassLoader。 Extensions ClassLoader查找JAVA_HOME/jre/lib/ext目录下或者-Djava.ext.dirs选项指定目录下的jar包,如果找到就返回,找不到则交给App ClassLoader。
App ClassLoade查找Classpath目录下或者-Djava.class.path选项所指定的目录下的jar包和Class文件,如果找到就返回,找不到交给我们自定义的类加载器,如果还找不到则抛出异常。
总的来说就是Class文件加载到类加载子系统后,先沿着图中红色虚线的方向自下而上进行委托,再沿着黑色虚线的方向自上而下进行查找,整个过程就是先上后下。

protected Class More ...loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            Class c = findLoadedClass(name);//1
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);//2
                    } else {
                        c = findBootstrapClassOrNull(name);//3
                    }
                } catch (ClassNotFoundException e) {            
                }
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);//4
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
           if (resolve) {
                resolveClass(c);
            }
           return c;
        }
    }

注释1处用来检查类是否已经加载,如果已经加载则后面的代码不会执行,最后会返回该类。没有加载则会接着向下执行。
注释2处,如果父类加载器不为null,则调用父类加载器的loadClass方法。如果父类加载器为null则调用注释3处的findBootstrapClassOrNull方法,这个方法内部调用了Native方法findBootstrapClass,findBootstrapClass方法中最终会用Bootstrap Classloader来查找类。如果Bootstrap Classloader仍没有找到该类,也就说明向上委托没有找到该类,则调用注释4处的findClass方法继续向下进行查找。

双亲委托模式的好处

避免重复加载,如果已经加载过一次Class,就不需要再次加载,而是先从缓存中直接读取。
更加安全,如果不使用双亲委托模式,就可以自定义一个String类来替代系统的String类,这显然会造成安全隐患,采用双亲委托模式会使得系统的String类在Java虚拟机启动时就被加载,也就无法自定义String类来替代系统的String类,除非我们修改
类加载器搜索类的默认算法。还有一点,只有两个类名一致并且被同一个类加载器加载的类,Java虚拟机才会认为它们是同一个类,想要骗过Java虚拟机显然不会那么容易。

参考链接

Android 插件化和热修复知识梳理
Android解析ClassLoader(一)Java中的ClassLoader

你可能感兴趣的:(热修复)