2018-03-10

一、Android 中的ClassLoader

1. PathClassLoader

在应用启动时创建,从data/app/.... 安装目录下加载apk文件
在Zygoteinit 中的调用是用来启动相关的系统服务
在ApplicationLoaders 中用来加载系统安过的apk,用来加载apk内的class

2. BootClassLoader

BootClassLoader 是PathClassLoader 的父加载器,其在系统启动时创建,在app启动时会将该对象传进来

3. DexClassLoader

DexClassLoader 则没有此限制,可以从sd卡上加载包含 class.dex的 .jar 和 .apk 文件,这也是插件化的和热修复的基础。在不需要安装的情况下,完成需要使用的Dex 的加载

二、ClassLoader 的双亲委托

protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{  
         // 首先检查该name指定的class是否有被加载  
         Class c = findLoadedClass(name);  
         if (c == null) {  
             try {  
                 if (parent != null) {  
                     //如果parent不为null,则调用parent的loadClass进行加载  
                     c = parent.loadClass(name, false);  
                 }else{  
                     //parent为null,则调用BootstrapClassLoader进行加载  
                     c = findBootstrapClass0(name);  
                 }  
             }catch(ClassNotFoundException e) {  
                 //如果仍然无法加载成功,则调用自身的findClass进行加载              
                 c = findClass(name);  
             }  
         }  
         if (resolve) {  
             resolveClass(c);  
         }  
         return c;  
    } 

为什么要使用这种双亲委托模式呢?

第一个原因就是因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。

第二个原因就是考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类 型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的 ClassLoader。

三、双亲委托在模型在热修复领域的应用

由于DexClassLoader 和 PathClassLoader 都是继承BaseClassLoader

BaseClassLoader 的源码

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 suppressedExceptions = new ArrayList();
        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;
    }

在BaseDexClassLoader构造函数中创建一个DexPathList类的实例,这个DexPathList 的构造函数会创建一个dexElements 数组

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

BaseClassLoader 重写了 findClass 方法,调用了pathList.findClass

/* package */final class DexPathList {
    ...
    public Class findClass(String name, List 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;
    }
    ...
} 

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

归纳上面的话就是:ClassLoader会遍历这个数组,然后加载这个数组中的dex文件.

而ClassLoader在加载到正确的类之后,就不会再去加载有Bug的那个类了,我们把这个正确的类放在Dex文件中,让这个Dex文件排在dexElements数组前面即可.

还有个问题

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

那么我们就要阻止被引用类打上CLASS_ISPREVERIFIED标志.QQ空间的方法是在所有引用到该类的构造函数中插入一段代码,代码引用到别的类.

你可能感兴趣的:(2018-03-10)