美文网首页我爱编程
Android 热修复开发原理——类加载机制源码详解

Android 热修复开发原理——类加载机制源码详解

作者: breaktian | 来源:发表于2018-05-11 17:20 被阅读106次

导语:

热修复说白了就是”打补丁”,通过事先设定的接口从网上下载无Bug的代码来替换有Bug的代码。这样就省事多了,用户体验也好。这样带来的优势就是成本低、效率高。热修复的特点:无需重新发版,实时高效热修复;用户无感知修复,无需下载新的应用,代价小;修复成功率高,把损失降到最低。可以查看上篇文章连接:Android热修复开源方案阿里、微信、美团等

但是希望深入了解热修复,还得看着一篇文章,细细研读,很简单。

一、DVM(Dalvik虚拟机)、 JVM(JAVA虚拟机)加载类原理:

1、JVM加载类机制

Java程序在运行的时候,JVM通过类加载机制(ClassLoader)把.class文件加载到内存中,只有class文件被载入内存,才能被其他class引用,使程序正确运行起来。(这句是精华)

ClassLoader有三种:

  1. Bootstrap ClassLoader

由C++写的,由JVM启动.

启动类加载器,负责加载java基础类,对应的文件是%JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等

2.Extension ClassLoader

Java类,继承自URLClassLoader

扩展类加载器,对应的文件是 %JRE_HOME/lib/ext 目录下的jar和class等

3.App ClassLoader

Java类,继承自URLClassLoader

系统类加载器,对应的文件是应用程序classpath目录下的所有jar和class等

三者关系:

Java的类加载使用双亲委托机制来搜索类.三种ClassLoader存在父子关系,App ClassLoader的父类加载器是Extension ClassLoader,Extension ClassLoader的父类加载器是Bootstrap ClassLoader,要注意的一点是,这里的父子并不是继承关系.

我新建一个类Test来验证三者的关系.

public class Test {  
  
    public static void main(String[] args) {  
        ClassLoader ClassLoader1 = Test.class.getClassLoader();  
        ClassLoader ClassLoader2 = ClassLoader1.getParent();  
        ClassLoader ClassLoader3 = ClassLoader2.getParent();  
  
        System.out.println(ClassLoader1);  
        System.out.println(ClassLoader2);  
        System.out.println(ClassLoader3);  
    }  
}  

看下输出结果

sun.misc.Launcher$AppClassLoader@1bbf1ca  
sun.misc.Launcher$ExtClassLoader@1ff0dde  
null  

2.加载机制

当这三者中的某个ClassLoader要加载一个类时,会先委托它的父类加载器尝试加载,一直往上,如果最顶级的父类加载器没有找到该类,那么委托者则亲自到特定的地方加载,如果没找到,那么就抛出异常ClassNotFoundException.这里画张图来表示下:

image

注意 : 只有被同一个类加载器实例加载并且文件名相同的class文件才被认为是同一个class!

2、DVM加载类机制 (安卓类加载机制)

安卓类加载机制分为两种:PathClassLoader、DexClassLoader,两者都继承自BaseDexClassLoader。

PathClassLoader代码

public class PathClassLoader extends BaseDexClassLoader {  
  
    public PathClassLoader(String dexPath, ClassLoader parent) {  
        super(dexPath, null, null, parent);  
    }  
  
    public PathClassLoader(String dexPath, String libraryPath,  
            ClassLoader parent) {  
        super(dexPath, null, libraryPath, parent);  
    }  
}   

DexClassLoader代码

public class DexClassLoader extends BaseDexClassLoader {  
  
    public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {  
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);  
    }  
}  

1.PathClassLoader :代码位于libcore\dalvik\src\main[Java](http://lib.csdn.net/base/java "Java 知识库")\dalvik\system\PathClassLoader.java
2.DexClassLoader :代码位于libcore\dalvik\src\main\java\dalvik\system\DexClassLoader.java
3.BaseDexClassLoader:代码位于libcore\dalvik\src\main\java\dalvik\system\BaseDexClassLoader.java

PathClassLoader :用来加载系统类和应用类.

DexClassLoader :用来加载jar、apk、dex文件.加载jar、apk也是最终抽取里面的Dex文件进行加载.

这里写图片描述

二、热修复原理:

根据DVM加载类机制进一步分析实现。

先看,BaseDexClassLoader 代码

public class BaseDexClassLoader extends ClassLoader {  
    private final DexPathList pathList;  
  
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,  
            String libraryPath, ClassLoader parent) {  
        super(parent);  
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);  
    }  
  
    @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;  
    }  

接着看它类中代码:首先看到声明了DexPathList类名为pathList,然后看它的构造函数中创建了DexPathList类的实例,跟着我左手右手一个慢动作...点进去看看:

public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) {  
        ...   
        this.definingContext = definingContext;  
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();  
        //创建一个数组  
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);  
        ...   
    }  

发现,这个DexPathList的构造函数会创建一个dexElements 数组,哎,先不管了... 回头继续看基佬BaseDexClassLoader类:

第13行重写了findClass方法,调用了pathList.findClass,跳到DexPathList类中。

会遍历这个数组,然后初始化DexFile,如果DexFile不为空那么调用DexFile类的loadClassBinaryName方法返回Class实例。

/* package */final class DexPathList {  
    ...  
    public Class findClass(String name, List<Throwable> suppressed) {  
            //遍历该数组  
        for (Element element : dexElements) {  
            //初始化DexFile  
            DexFile dex = element.dexFile;  
  
            if (dex != null) {  
                //调用DexFile类的loadClassBinaryName方法返回Class实例  
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);  
                if (clazz != null) {  
                    return clazz;  
                }  
            }  
        }         
        return null;  
    }  
    ...  
}   

总而言之:ClassLoader会遍历这个数组,然后加载这个数组中的dex文件, 而ClassLoader在加载到正确的类(classloader在加载类的时候会先判断是是否已经加载过这个类,如果已经加载过就不会再次加载了)之后,就不会再去加载有Bug的那个类了,我们把这个正确的类放在Dex文件中,让这个Dex文件排在dexElements数组前面即可。

问题:这里有个question

如果引用者和被引用者的类(直接引用关系)在同一个Dex时,那么在虚拟机启动时,被引用类就会被打上CLASS_ISPREVERIFIED标志,这样被引用的类就不能进行热修复操作了。怎么办?

答:那么我们就要阻止被引用类打上CLASS_ISPREVERIFIED标志,QQ空间的方法是在所有引用到该类的构造函数中插入一段代码,代码引用到别的类.可参考QQ空间团队的 安卓App热补丁动态修复技术介绍

相关文章

网友评论

    本文标题:Android 热修复开发原理——类加载机制源码详解

    本文链接:https://www.haomeiwen.com/subject/wqpurftx.html