第十周

作者: hoyouly | 来源:发表于2018-03-11 13:29 被阅读8次

    Android中有哪几种ClassLoader?它们的作用和区别是什么?

    BootClassLoader,URLClassLoader,PathClassLoader,DexClassLoader,BaseDexClassLoader等,这些最终都继承自java.lang.ClasssLoader
    关系图如下:


    Android 平台的ClassLoader

    BootCLassLoader

    和JVM不同的是,BootClassLoader是ClassLoader的内部类,由java代码实现而不是C++,是Android平台所有ClassLoader的最终Parent,这个类是包内可见,所以我们无法使用。

    URLClassLoader

    只能用于jar文件,但是由于Dalvik不能识别jar,所以在Android平台上无法使用这个加载器

    BaseDexClassLoader

    DexClassLoader和PathClassLoader 都继承BaseDexClassLoader,其中主要逻辑都是在BaseDexClassLoader完成的。是一个基础的加载器,
    先看BaseDexClassLoader的构造函数

      /**
         * @param dexPath         指目标类所在的APK或jar文件路径,
         * @param optimizedDirectory   优化目录 
         * @param libraryPath       指的是目标所使用的c/c++库存放的路径
         * @param parent           指该加载器的父加载器,
         */
        public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
            super(parent);
            this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
        }
    

    四个参数的解释:

    • dexPath 类加载器将从该路径中寻找指定的目标类,
      • 该类必须是APK或者jar的全路径如果要包括多个路径,
      • 路径之间必须使用指定的分隔符,可以使用System.getProperty("path.separtor"),
      • DexClassLoader 中所谓的支持加载APK,jar,dex,也可以从SD卡中加载,指的就是这个路径,
      • 最终是将dexPath路径上的文件ODEX优化到optimizedDirectoyr,然后进行加载
    • optimizedDirectory
      • 由于dex文件被包含在APK或者jar中,因此在装载目标类之前需要从APK或者JAR文件中解压dex文件,该参数就是定制解压出来的dex文件存放的路径的,这也是对apk的dex根据平台进行ODE优化的过程,
      • 其实APK 是一个压缩包,里面包括dex文件,ODEX文件的优化就是把包里面的执行程序提取出来,就变成ODEX文件,因为你提取出来了,系统第一次启动的时候就不用去解压程序压缩包里面的程序,少了一个解压过程,这样系统启动就加快,
      • 为什么说是第一次呢?因为DEX版本只会有在第一次会解压执行程序到/data/dalvik-cache(针对PathClassLoder)或者optimizedDirectory(针对DexClassLoader)目录,之后也就直接读取dex文件,所以第二次启动就和正常启动差不多了,当然这只是简单理解,实际生成ODEX还有一定的优化作用,
      • ClassLoader只能加载内部存储路径中的dex文件,所以这个路径必须是内部路径
    • libraryPath 指的是目标所使用的c/c++库存放的路径
    • parent 一般为当前执行类的加载器,例如Android中以context.getClassLoader()作为父加载器

    PathClassLoader

    继承BaseDexClassLoader,构造函数如下

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

    很显然,PathClassLoader把optimizedDirectory设置为null,也就是没有设置优化后存放的路径,其实optimizedDirectory为null的默认路径就是/data/dalivk-cache目录
    在Dalivk虚拟机上只能加载已安装的APK的dex,在ART虚拟机上,可以加载未按照的APK的dex
    主要用于系统和app的类加载器
    在应用启动的时候创建,从data/app/安装目录下加载apk文件
    在Android中,app 安装到手机中,apk的dex文件中的class文件都是通过PathClassLoader来加载的

    DexClassLoader

    继承BaseDexClassLoader,构造函数如下

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

    支持加载APK,JAR,和DEX,也可以从SD卡上进行加载
    虽然说dalivk不能直接识别jar,但是却说DexClassLoader却能加载JAR文件,其实这个不矛盾,因为在BaseDexClassLoader里面对“.jar”,".apk",".zip",".dex"后缀的文件最后都会生成对应的dex文件,所以最终处理的还是dex文件,而URLClassLoader却没有做这样的处理,所以我们一般都是用DexClassLoader来作为动态加载的加载器
    可以从包含classes.dex的jar或者APK 中加载类的加载器,用于执行动态加载,但必须是app私有可写目录来缓存ODEX 文件,能够加载系统没按照的apk或者jar文件,因此很多插件化方案都是用DexClassLoader

    摘抄:
    Android动态加载之ClassLoader详解
    Android类加载器ClassLoader

    简述ClassLoader的双亲委托模型

    我们知道,每一个加载器都有一个父加载器,这个双亲委托模型就和这个父加载器有关:如果一个加载器收到了加载类的请求,他首先不会自己尝试去加载这个类,而是把这个请求交给他的父类加载器完成,父类加载器也有父类加载器的,每个加载器都是如此,只有当父加载器在自己搜索范围内找不到指定的类时( ClassNotFoundException),子加载器才会尝试自己去加载。
    具体加载过程如下:

    1. 源ClassLoader判断是否加载过该class,如果加载过,则直接返回该Class,如果没有则委托父类加载,
    2. 父类加载器判断是否加载过该Class,如果加载过,则直接返回该Class,如果没有则委托父类的父类,也就是爷爷类加载器
    3. 以此类推,直到始祖类加载器(引用类加载器)
    4. 始祖类加载器判断是否加载过该Class,如果加载过,则直接返回该Class,如果没有,则尝试从其对应的类的路径下寻找class字节码并载入,如果载入成功,则直接返回Class,如果载入失败,则委托给始祖类的子类加下载器
    5. 始祖类的子类加载器尝试从其对应的类的路径下寻找class字节码并载入,如果载入成功,则直接返回该Class,如果载入失败,则委托给始祖类子类的子类,即孙子类加载器
    6. 以此类推,直到源ClassLoader
    7. 源ClassLoader尝试从其对应的类的路径下寻找class字节码并载入,如果载入成功,则直接返回该class,如果载入失败,源ClassLoader不会在委托其他子类加载器,而是抛出异常。

    委托机制的意义:防止内存中出现多份同样的字节码,例如如果A类和B类都需要加载一个System,如果不用委托机制,而是自己加载自己,那么A类会加载一份System字节码.B 类又会加载一份System 字节码,这样内存中就会出现两份System字节码。

    试想一个场景:黑客自定义一个java.lang.String类,该String类具有系统的String类一样的功能,只是在某个函数稍作修改。比如equals函数,这个函数经常使用,如果在这这个函数中,黑客加入一些“病毒代码”。并且通过自定义类加载器加入到JVM中。此时,如果没有双亲委派模型,那么JVM就可能误以为黑客自定义的java.lang.String类是系统的String类,导致“病毒代码”被执行。而有了双亲委派模型,黑客自定义的java.lang.String类永远都不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类。

    参考: 热修复入门:Android 中的 ClassLoader

    简述双亲委托模型在热修复领域的应用

    在用BaseDexClassLoader或者DexClassLoader去加载某个dex或者某个apk中的class时候,无法调用findClass()方法,因为该方法是包访问权限的,需要调用loadClass(),该方法其实是BaseDexClassLoader的父类ClassLoader实现的,findclass()方法就是双亲委托模型的实际体现。源码如下

     public Class<?> loadClass(String className) throws ClassNotFoundException {
            return loadClass(className, false);
        }
     protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
            Class<?> clazz = findLoadedClass(className);//查找是否加载过该Class 文件
    
            if (clazz == null) {//没有加载过,
                try {
                    //调用父类的loadClass()方法去加载该文件
                    clazz = parent.loadClass(className, false);
                } catch (ClassNotFoundException e) {
                    // Don't want to see this.
                }
    
                if (clazz == null) {//父类没有加载过或者加载失败,
                    clazz = findClass(className);//自己去加载
                }
            }
            //说明加载过,直接返回
            return clazz;
        }
    

    相关文章

      网友评论

          本文标题:第十周

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