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

不管是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初始化
     /**
         * Encapsulates the set of parallel capable loader types.
         */
        private static ClassLoader createSystemClassLoader() {
            //系统framework下文件路径
            String classPath = System.getProperty("java.class.path", ".");
            // native c/c++ 的library包
            String librarySearchPath = System.getProperty("java.library.path", "");
    
            // TODO Make this a java.net.URLClassLoader once we have those?
            return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
        }

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

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

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

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

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

  3. PathClassloader
    public class PathClassLoader extends BaseDexClassLoader {
        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!");
        }
    }

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

  4. BaseDexClassloader
    最重要的base加载器,可以看到PathClassloader和DexClassloader都是继承自他的,所以几乎大部分逻辑都被放到了BaseClassloader中。
     private final DexPathList pathList;
    
     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();
            }
        }

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

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

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

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

    //待封装的 dex / apk文件数组
    Element[] elements = new Element[files.size()];
          int elementsPos = 0;
          /*
           * Open all files and load the (direct or contained) dex files up front.
           */
          for (File file : files) {
              if (file.isDirectory()) {
                  // 是文件夹直接 加入到 数组中。
                  elements[elementsPos++] = new Element(file);
              } else if (file.isFile()) {
                  String name = file.getName();
    
                  DexFile dex = null;
                  // 是文件先判断 后缀名,然后把file 转换到 DexFile 数据结构
                  if (name.endsWith(DEX_SUFFIX)) {
                      // Raw dex file (not inside a zip/jar).
                      try {
                          // 转换到 DexFile 数据结构
                          dex = loadDexFile(file, optimizedDirectory, loader, elements);
                          if (dex != null) {
                              elements[elementsPos++] = new Element(dex, null);
                          }
                      } catch (IOException suppressed) {
                          
                      }
                  } else {
                     
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                      // 为 null 说明不是 dex 文件,仅仅是个文件
                      if (dex == null) {
                          elements[elementsPos++] = new Element(file);
                      } else {
                          elements[elementsPos++] = new Element(dex, file);
                      }
                  }
                 
          }
         
          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对象。

 ClassLoader cl = null;
            if (systemServerClasspath != null) {
                cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
                Thread.currentThread().setContextClassLoader(cl);
            }
            /*
             * Pass the remaining arguments to SystemServer.
             */
            return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);

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

  public static ClassLoader createClassLoader(String dexPath,
            String librarySearchPath, ClassLoader parent, String classloaderName) {
        if (isPathClassLoaderName(classloaderName)) {
            return new PathClassLoader(dexPath, librarySearchPath, parent);
        } else if (isDelegateLastClassLoaderName(classloaderName)) {
            return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
        }

        throw new AssertionError("Invalid classLoaderName: " + classloaderName);
    }

加载流程

问题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混合编译与对热补丁影响解析

发布了119 篇原创文章 · 获赞 140 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/WangRain1/article/details/103504590