Android ClassLoader

1. ClassLoader的定义

  • 将Class字节码转换成内存中的Class对象,实现Class的加载
    • Class字节码本质就是字节数组

2. Android ClassLoader 结构

ClassLoader.PNG

  • Android之中的ClassLoader有PathClassLoader和DexClassLoader
    • 这两种ClassLoader都派生自BaseDexClassLoader
  • BaseDexClassLoader是ClassLoader的子类
    • DexPathList是优化后的Dex列表
    • BootClassLoader是BaseDexClassLoader的内部类
    • URLClassLoader在android系统中无用

2.1. BootClassLoader

  • 和java虚拟机中不一样的是BootClassLoader是ClassLoader内部类
  • 由java代码实现而不是c++实现
  • 是Android平台上全部ClassLoader的最终parent
  • 这个内部类是包内可见,因此咱们无法使用

2.2. URLClassLoader

  • 只能用于加载jar文件,可是因为 dalvik 不能直接识别jar,因此在 Android 中没法使用这个加载器

2.3. DexClassLoader

  • DexClassLoader支持加载APK、DEX和JAR,也能够从SD卡进行加载
  • 在BaseDexClassLoader里对JAR,ZIP,APK,DEX后缀的文件最后都会生成一个对应的DEX文件,因此最终处理的仍是dex文件,而URLClassLoader并无作相似的处理

2.4. PathClassLoader

  • PathClassLoader将optimizedDirectory置为Null,也就是没设置优化后的存放路径
  • optimizedDirectory为null时的默认路径就是/data/dalvik-cache 目录

2.4.1. PathClassLoader和DexClassLoader的区别|API26以前

  • DexClassLoader可指定optimizedDirectory

2.4.2.PathClassLoader和DexClassLoader无区别| API26以后

  • 其实在Android8.0|API26以后,系统是不容许指定optimizedDirectory目录的,也就致使根本上无区别,源码以下:
public PathClassLoader(String dexPath, ClassLoader parent) {
    super((String)null, (File)null, (String)null, (ClassLoader)null);
    throw new RuntimeException("Stub!");
}

public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
    super((String)null, (File)null, (String)null, (ClassLoader)null);
    throw new RuntimeException("Stub!");
}

public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }

public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {
    this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
}

   public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent, boolean isTrusted) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

    if (reporter != null) {
        reportClassLoaderChain();
    }
}
    
复制代码

3. BaseDexClassLoader

3.1. 构造参数

//构造方法
	public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
    }
   	/** * @hide */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent, boolean isTrusted) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
        if (reporter != null) {
            reportClassLoaderChain();
        }
    }
    /** * Reports the current class loader chain to the registered {@code reporter}. * The chain is reported only if all its elements are {@code BaseDexClassLoader}. */
    private void reportClassLoaderChain() {
        ArrayList<BaseDexClassLoader> classLoadersChain = new ArrayList<>();
        ArrayList<String> classPaths = new ArrayList<>();
        classLoadersChain.add(this);
        classPaths.add(String.join(File.pathSeparator, pathList.getDexPaths()));
        boolean onlySawSupportedClassLoaders = true;
        ClassLoader bootClassLoader = ClassLoader.getSystemClassLoader().getParent();
        ClassLoader current = getParent();
        while (current != null && current != bootClassLoader) {
            if (current instanceof BaseDexClassLoader) {
                BaseDexClassLoader bdcCurrent = (BaseDexClassLoader) current;
                classLoadersChain.add(bdcCurrent);
              	classPaths.add(String.join(File.pathSeparator, bdcCurrent.pathList.getDexPaths()));
            } else {
               onlySawSupportedClassLoaders = false;
                break;
            }
            current = current.getParent();
        }
        if (onlySawSupportedClassLoaders) {
            reporter.report(classLoadersChain, classPaths);
        }
    }
复制代码
  • String dexPath
    • 目标类所在的APK或者JAR文件的全路径,类加载器对其进行加载
    • 若是要同时加载多个路径,经过**System.getProperty(“path.separtor”)**获取分隔符进行分割
    • 将dexPath路径上的文件ODEX优化到内部位置optimizedDirectory之中,对其进行加载
  • File optimizedDirectory
    • 因为DEX文件包含在JAR或者APK文件之中,因此要对其进行解压以获取DEX文件,此参数指定了解压目录
    • 当系统首次启动的时候直接获取的是解压后的DEX(ODEX)
    • ClassLoader只能加载内部存储路径中的dex文件,因此这个路径必须为内部路径。
  • String librarySearchPath
    • 目标类之中的C/C++库的存放路径
  • ClassLoader parent
    • 该类加载器的父类加载器,通常为当前执行类的装载器,例如在Android中以context.getClassLoader()做为父装载器。

3.2. loadClass

  • 用于控制加载Class的入口
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                c = findClass(name);
            }
        }
        return c;
}
复制代码
  • 首先判断这个类是否以前被加载过,若是有则直接返回
  • 若是没有则首先尝试让parent ClassLoader进行加载,加载不成功才在本身的findClass中进行加载。

3.2.1. parents delegate

  • 和java虚拟机中常见的parents delegate模型一致的,这种模型并非一个强制性的约束模型
  • 好比你能够继承ClassLoader复写loadCalss方法来破坏这种模型,只不过parents delegate是一种被推荐的实现类加载器的方式
  • jdk1.2之后已经不提倡用户在覆盖loadClass方法,而应该把本身的类加载逻辑写到findClass中。
  • parents delegate机制为的是放置类重复加载,先有父亲进行循环分析。

3.2.2. 共享与隔离

  • 实现共享,一些Framework层级的类一旦被顶层的ClassLoader加载过就缓存在内存里面,之后任何地方用到都不须要从新加载。
  • 实现隔离,不一样继承路线上的ClassLoader加载的类确定不是同一个类,这样的限制避免了用户本身的代码冒充核心类库的类访问核心类库包可见成员的状况。

3.3. findClass

  • 用于经过本身内部的逻辑获取目标类的字节码
@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;
    }

	public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) { return clazz;}
        }
        if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));}
        return null;
    }
复制代码
  • pathList内部包含的就是ODEX集合(优化后的dex),经过findClass获取到全部dexElements,最终调用findClass获取Class。

3.4. defineClass|非Android平台

  • 用于将字节码转换成Class对象,可是在Android平台是不被容许的
@Deprecated
protected final Class<?> defineClass(byte[] b, int off, int len)
    throws ClassFormatError
{
    throw new UnsupportedOperationException("can't load this type of class file");
}
复制代码

4.ART虚拟机

Android Runtime(缩写为ART),在Android 5.0及后续Android版本中做为正式的运行时库取代了以往的Dalvik虚拟机。ART可以把应用程序的字节码转换为机器码,是Android所使用的一种新的虚拟机。java

它与Dalvik的主要不一样在于:android

  • Dalvik采用的是JIT技术,字节码都须要经过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率,
  • ART采用Ahead-of-time(AOT)技术,应用在第一次安装的时候,字节码就会预先编译成机器码,这个过程叫作预编译。ART同时也改善了性能、垃圾回收(Garbage Collection)、应用程序除错以及性能分析

可是,运行时内存占用空间较少一样意味着编译二进制须要更高的存储c++

  • ART模式相比原来的Dalvik,会在安装APK的时候,使用Android系统自带的dex2oat工具把APK里面的.dex文件转化成OAT文件,OAT文件是一种Android私有ELF文件格式,它不只包含有从DEX文件翻译而来的本地机器指令,还包含有原来的DEX文件内容。这使得咱们无需从新编译原有的APK就可让它正常地在ART里面运行,也就是咱们不须要改变原来的APK编程接口
  • ART模式的系统里,一样存在DexClassLoader类,包名路径也没变,只不过它的具体实现与原来的有所不一样,可是接口是一致的。实际上,ART运行时就是和Dalvik虚拟机同样,实现了一套彻底兼容Java虚拟机的接口
  • class:java 编译后的⽂件,每一个类对应⼀个 class ⽂件
  • dex:Dalvik EXecutable 把 class 打包在⼀起,⼀个 dex 能够包含多个 class ⽂件
  • odex:Optimized DEX 针对系统的优化,例如某个⽅法的调⽤指令,会把虚拟的调⽤转换为使⽤具体的 index,这样在执⾏的时候就不⽤再查找了
  • oat:Optimized Android fifile Type。使⽤ AOT 策略对 dex 预先编译(解释)成本地指令,这样再运⾏阶段就不需再经历⼀次解释过程,程序的运⾏能够更快AOT:Ahead-Of-Time compilation 预先编译

5.DEX文件

  • class:java 编译后的⽂件,每一个类对应⼀个 class ⽂件
  • dex:Dalvik EXecutable 把 class 打包在⼀起,⼀个 dex 能够包含多个 class ⽂件
  • odex:Optimized DEX 针对系统的优化,例如某个⽅法的调⽤指令,会把虚拟的调⽤转换为使⽤具体的 index,这样在执⾏的时候就不⽤再查找了
  • oat:Optimized Android fifile Type。使⽤ AOT 策略对 dex 预先编译(解释)成本地指令,这样再运⾏阶段就不需再经历⼀次解释过程,程序的运⾏能够更快
    • AOT:Ahead-Of-Time compilation 预先编译
相关文章
相关标签/搜索