小言_互联网的博客

源码学习《6》Classloader 类加载机制 (热修复 1)原理篇

312人阅读  评论(0)

不管是android 还是java项目我们知道我们的java文件都会通过 javac命令编译成二进制class文件,然后虚拟机再通过classloader类对class文件进行加载生成Class对象。其中java 和 android的classloader实现上还是有区别的,java主要加载的是 .class 而 android加载的是.dex 文件。本篇主要分析android classloader源码,对java 只做大致介绍。

1. Classloader 作用 ?

      一个程序在运行时候,我们的逻辑代码java文件都被编译成class文件,在我们程序的入口处调用其他class文件时候,由于java程序是动态加载的,如果其他class文件不存在,程序就会崩溃,这时候就需要把class文件通过类加载器加载到内存中去, 然后其他class文件才能使用当前被加载到内存的class文件,这就是classloader的作用。   

2. Java 中的Classloader

java中有Bootstrap Classloader , Extensions ClassLoader和 App ClassLoader 。其中Bootstrap Classloader 是加载C/C++的类加载对象,而 Extensions ClassLoader和 App ClassLoader 是继承自 Classloader抽象类的,也是我们java代码使用的加载机制。(不做过多介绍)

3. Android 中的Classloader

首先在android中Classloader是个静态类,子类主要有 BaseDexClassloader,BootClassloader,PathClassloader和DexClassloader。继承关系如下图:

  1. Classloader 一个抽象类,让子类重写findClass() 方法,并且实现loadClass()方法双亲委托机制。
  2. BootClassloader 主要用于加载系统源码,例如:framework下的代码。
  3. BaseDexClassloader 加载dex文件的主要功能实现,内部包括 DexPathLIst类Element数组对象。
  4. DexClassloader 主要用于加载jar或者外部class文件。
  5. PathClassloader 主要用于加载已经安装到手机上的apk内部的dex。

整个Classloader架构核心的代码都在BaseDexClassloader中的 DexPathlist 类,下面就大致分析一下代码流程:

4. Anddrod Classloader 源码流程 

  1. Classloader抽象类
    在Classloader内部首先会对bootClassloader初始化
    
        
    1. /**
    2. * Encapsulates the set of parallel capable loader types.
    3. */
    4. private static ClassLoader createSystemClassLoader() {
    5. //系统framework下文件路径
    6. String classPath = System.getProperty( "java.class.path", ".");
    7. // native c/c++ 的library包
    8. String librarySearchPath = System.getProperty( "java.library.path", "");
    9. // TODO Make this a java.net.URLClassLoader once we have those?
    10. return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
    11. }

    除了系统类加载器的创建,还有一个非常重要的方法代码

    
        
    1. protected Class<?> loadClass( String name, boolean resolve)
    2. throws ClassNotFoundException
    3. {
    4. // First, check if the class has already been loaded
    5. // 首先到已经加载过的内存中去查找,找到了就返回,否则委托父节点去查询
    6. Class<?> c = findLoadedClass(name);
    7. if ( c == null) {
    8. try {
    9. if (parent != null) {
    10. c = parent.loadClass(name, false);
    11. } else {
    12. c = findBootstrapClassOrNull(name);
    13. }
    14. } catch ( ClassNotFoundException e) {
    15. // ClassNotFoundException thrown if class not found
    16. // from the non-null parent class loader
    17. }
    18. if ( c == null) {
    19. // If still not found, then invoke findClass in order
    20. // to find the class.
    21. c = findClass(name);
    22. }
    23. }
    24. return c;
    25. }

    这段代码就是Classloader双亲委托机制的代码实现,下面会详细介绍 什么是双亲委托机制。

  2. DexClassloader
    
        
    1. public class DexClassLoader extends BaseDexClassLoader {
    2. public DexClassLoader( String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
    3. super(( String) null, (File) null, ( String) null, (ClassLoader) null);
    4. throw new RuntimeException( "Stub!");
    5. }
    6. }

    dexPath : dex文件的路径,多个用 “:” 分开
    optimizedDirectory : 解压的dex文件存储路径,这个路径必须是一个内部存储路径,一般情况下使用当前应用程序的私有路径:/data/data/<Package Name>/...
    librarySearchPath :native 的library路径,可以为null
    ClassLoader parent : 传入的父classloader,用于委托查询

  3. PathClassloader
    
        
    1. public class PathClassLoader extends BaseDexClassLoader {
    2. public PathClassLoader( String dexPath, ClassLoader parent) {
    3. super(( String) null, (File) null, ( String) null, (ClassLoader) null);
    4. throw new RuntimeException( "Stub!");
    5. }
    6. public PathClassLoader( String dexPath, String librarySearchPath, ClassLoader parent) {
    7. super(( String) null, (File) null, ( String) null, (ClassLoader) null);
    8. throw new RuntimeException( "Stub!");
    9. }
    10. }

    可以看到 PathClassloader 比较容易理解,与DexClassloader参数意思相同。

  4. BaseDexClassloader
    最重要的base加载器,可以看到PathClassloader和DexClassloader都是继承自他的,所以几乎大部分逻辑都被放到了BaseClassloader中。
    
        
    1. private final DexPathList pathList;
    2. public BaseDexClassLoader(String dexPath, File optimizedDirectory,
    3. String librarySearchPath, ClassLoader parent, boolean isTrusted) {
    4. super(parent);
    5. this.pathList = new DexPathList( this, dexPath, librarySearchPath, null, isTrusted);
    6. if (reporter != null) {
    7. reportClassLoaderChain();
    8. }
    9. }

    经过分析BaseClassloader中的方法可以看到,BaseClassloader的功能几乎都是调用了 DexPathList 类,而且是在构造方法中初始化的。接下里主要分析DexPathList的结果。

  5. DexPathList 结构
    在DexPathList中保存了一个Element的数据结构,在构造方法中初始化Element数组

    
        
    1. private Element[] dexElements;
    2. DexPathList(ClassLoader definingContext, String dexPath,
    3. String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
    4. this.definingContext = definingContext;
    5. ..........
    6. // save dexPath for BaseDexClassLoader
    7. this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
    8. suppressedExceptions, definingContext, isTrusted);
    9. // Native libraries may exist in both the system and
    10. // application library paths, and we use this search order:
    11. //
    12. // 1. This class loader's library path for application libraries (librarySearchPath):
    13. // 1.1. Native library directories
    14. // 1.2. Path to libraries in apk-files
    15. // 2. The VM's library path from the system property for system libraries
    16. // also known as java.library.path
    17. //
    18. // This order was reversed prior to Gingerbread; see http://b/2933456.
    19. .................
    20. }

    在这里根据传来的file路径,分割得到每个dex文件或者apk文件,然后放入到dexElements数组中去。看makeDexElement()方法

    
        
    1. //待封装的 dex / apk文件数组
    2. Element[] elements = new Element[files.size()];
    3. int elementsPos = 0;
    4. /*
    5. * Open all files and load the (direct or contained) dex files up front.
    6. */
    7. for (File file : files) {
    8. if (file.isDirectory()) {
    9. // 是文件夹直接 加入到 数组中。
    10. elements[elementsPos++] = new Element(file);
    11. } else if (file.isFile()) {
    12. String name = file.getName();
    13. DexFile dex = null;
    14. // 是文件先判断 后缀名,然后把file 转换到 DexFile 数据结构
    15. if (name.endsWith(DEX_SUFFIX)) {
    16. // Raw dex file (not inside a zip/jar).
    17. try {
    18. // 转换到 DexFile 数据结构
    19. dex = loadDexFile(file, optimizedDirectory, loader, elements);
    20. if (dex != null) {
    21. elements[elementsPos++] = new Element(dex, null);
    22. }
    23. } catch (IOException suppressed) {
    24. }
    25. } else {
    26. dex = loadDexFile(file, optimizedDirectory, loader, elements);
    27. // 为 null 说明不是 dex 文件,仅仅是个文件
    28. if (dex == null) {
    29. elements[elementsPos++] = new Element(file);
    30. } else {
    31. elements[elementsPos++] = new Element(dex, file);
    32. }
    33. }
    34. }
    35. return elements;

    在这里把所有dex/apk等文件放入 element 数组中,等待 findClass()方法调用

5. 双亲委托机制

通过对 Classloader 中的loadClass()方法的代码分析,知道大致流程图

当我们有一个class文件需要加载时,首先会到我们自定义的MyClassloader中去查询,查不到就会委托内部持有的parent classloader(PathClassloader)中查询,如果查不到,在继续往上委托到boot classloader中查找。直到整个委托过程结束后,再从boot classloader 向 指定的 path 路径下搜索 class 文件,一次类推,直到Myclassloader 到指定路径下去查 class 文件,查到就 load 到内存(一般用 io 流加载),否则返回 null。

6. PathClassloader 的初始化

学过android系统启动流程,容易知道。当Zygote进程启动之后,就会创建SystemServer进程。在SystemServer进程被fork()之后,在通过反射调用 SystemServer 的 main() 方法 之前,传入了一个classloader,这个classloader就是 pathClassloader对象。


  
  1. ClassLoader cl = null;
  2. if (systemServerClasspath != null) {
  3. cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
  4. Thread.currentThread().setContextClassLoader(cl);
  5. }
  6. /*
  7. * Pass the remaining arguments to SystemServer.
  8. */
  9. return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);

其中createPathClassloader()方法 返回了cl 对象。


  
  1. public static ClassLoader createClassLoader(String dexPath,
  2. String librarySearchPath, ClassLoader parent, String classloaderName) {
  3. if (isPathClassLoaderName(classloaderName)) {
  4. return new PathClassLoader(dexPath, librarySearchPath, parent);
  5. } else if (isDelegateLastClassLoaderName(classloaderName)) {
  6. return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
  7. }
  8. throw new AssertionError( "Invalid classLoaderName: " + classloaderName);
  9. }

加载流程

问题1.自己定义一个和系统包名一样的String类的时候,根本不会引用到我们自定义的String为什么?
因为string类在zygote进程启动的时候使用bootclassloader先把jdk等class先加载到内存了,等到我们app再用pathclassloader去加载string时候会委托到bootclassloader查询,然后就查询到了string对象。

问题2.为什么Tinker不使用DexClassLoader加载再反射获取它里面的Element?而是要反射makePathElement这样的方法把dex生成Element?
因为:就是A和B类,A调用了B,如果B被内联。那A和B要是同一个classloader加载的。如果A在补丁包,你用dexclassloader加载A,那不就不在同一个classloader了。

参考:

1. https://blog.csdn.net/xyang81/article/details/7292380

2.  https://blog.csdn.net/itachi85/article/details/78088701

3. https://blog.csdn.net/itachi85/article/details/78276837

4. https://www.jianshu.com/p/96a72d1a7974

推荐:

https://www.diycode.cc/topics/321

Android N混合编译与对热补丁影响解析


转载:https://blog.csdn.net/WangRain1/article/details/103504590
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场