美文网首页
2018-03-10

2018-03-10

作者: 满脸胡渣的年轻大叔 | 来源:发表于2018-03-10 21:58 被阅读7次

    一、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<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;
        }
    

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

    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);
            ... 
        }
    

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

    /* 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;
        }
        ...
    } 
    

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

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

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

    还有个问题

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

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

    相关文章

      网友评论

          本文标题:2018-03-10

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