​#Android#​

概述

Android与JVM加载机制相同点:

  • 都需要使用到ClassLoader将目标类加载到内存

Android与JVM加载机制差异点:

  • Jvm主要读取class字节码
  • art主要读取dex字节码, 这是更好的方案, 可以将多个class文件合并成一个classes.dex文件

关系类图

image

类图分析

类图学习: UML类图

  • ClassLoader是一父类, 继承它的类有: BootClassLoader, BaseDexClassLoader, WarningContextClassLoader
  • BaseDexClassLoader与DexPathList类之间是组合关系, BaseDexClassLoader是拥有者, 同时当BaseDexClassLoader生命期结束时, 所属DexPathList对象的生命期也结束.
  • BaseDexClassLoader是ClassLoader的子类, 同时也有子类: DexClassLoader, PathClassLoader

五种构造器

照层次结构查看各个构造器

PathClassLoader

父加载器是BaseDexClassLoader

注意传给父加载器BaseDexClassLoader的optimizedDirectory参数为null

public class PathClassLoader extends BaseDexClassLoader {

    public PathClassLoader(String dexPath, ClassLoader parent) { // 构造方法: 需要传入父加载器parent, 还有需要加载的dex路径
        super(dexPath, null, null, parent); // 直接传递给父加载器(跟JVM的好像不太一样??, 没有先判断该类是否已加载到内存, 直接就传给父加载器了. 也可能还没有实现)
    					    // 注意传给父加载器BaseDexClassLoader的optimizedDirectory参数为null
    }

    public PathClassLoader(String dexPath, String libraryPath, 
            ClassLoader parent) { // 另一个构造方法, 多了一个libraryPath, 应该时运行库的路径
        super(dexPath, null, libraryPath, parent);
    }
}

DexClassLoader

父加载器也是BaseDexClassLoader

PathClassLoader相比, 多传入了optimizedDirectory参数

public class DexClassLoader extends BaseDexClassLoader {

    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) { // 又多了一个优化目录optimizedDirectory参数
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

BaseDexClassLoader

简要说明

  • 是PathClassLoader和DexClassLoader的父类
  • 其父类是ClassLoader
  • 有一个非常重要的步骤: 初始化了DexPathList对象 (在上面的类图中也出现了, 与BaseDexClassLoader是组合关系)

参数说明

  • dexPath: 包含目标类或资源的apk/jar列表, 当有多个路径时使用":"分割
  • optimizedDirectory: 优化后dex文件存在的目录, 可以为null
  • libraryPath: native库所在路径列表, 多个路径使用":"分割
  • ClassLoader: 父加载器
public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;  //记录dex文件路径信息

    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { // 参数列表与DexClassLoader的构造方法一样
        super(parent); // 直接传给父加载器
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
}

ClassLoader (抽象类)

注意的点:

  • ClassLoader无参构造方法, 默认将SystemClassLoader作为父加载器
  • 第三个构造方法的boolean参数表示是否允许父加载器为空, 如果false且父加载器为空, 则抛出异常
public abstract class ClassLoader {
    private ClassLoader parent;  //记录父类加载器

    protected ClassLoader() { // 无参构造方法, 默认将父加载器设置为SystemClassLoader
        this(getSystemClassLoader(), false); //调用重载构造方法, 见下文
    }

    protected ClassLoader(ClassLoader parentLoader) {
        this(parentLoader, false);
    }

    ClassLoader(ClassLoader parentLoader, boolean nullAllowed) { // 重载构造方法
        if (parentLoader == null && !nullAllowed) {
            //父类的类加载器为空,则抛出异常
            throw new NullPointerException("parentLoader == null && !nullAllowed");
        }
        parent = parentLoader;
    }
}

SystemClassLoader

上面的getSystemClassLoader()​方法返回的是PathClassLoader (一般自定义的ClassLoader继承了ClassLoader类, 然后属于最底层的ClassLoader, 默认以PathClassLoader作为父加载器的话就不奇怪了)

public abstract class ClassLoader {

    static private class SystemClassLoader {
        public static ClassLoader loader = ClassLoader.createSystemClassLoader();
    }

    public static ClassLoader getSystemClassLoader() {
        return SystemClassLoader.loader;
    }

    private static ClassLoader createSystemClassLoader() {
        //此处classPath默认值为"."
        String classPath = System.getProperty("java.class.path", ".");
        // BootClassLoader见小节2.5
        return new PathClassLoader(classPath, BootClassLoader.getInstance());
    }
}

BootClassLoader

注意的点:

  • 无参构造方法中: 传入的是null 和 true, 表示父加载器待定
class BootClassLoader extends ClassLoader {
    private static BootClassLoader instance;

    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }

    public BootClassLoader() { // 无参构造方法
        super(null, true);
    } }

PathClassLoader加载类的过程

PathClassLoader加载类过程是一个实例, 来简述ClassLoader的具体加载过程

大致流程:

  • 初始化
  • 执行loadClass()​方法加载相应的类 (当然需要先双亲委派, 这里主要讲加载流程, 这个就不细讲了)

初始化

public class PathClassLoader extends BaseDexClassLoader {

    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);  //见下文
    }
}

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
        String libraryPath, ClassLoader parent) {
        super(parent);  //见下文
        //收集dex文件和Native动态库【见小节3.2】
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
}

public abstract class ClassLoader {
    private ClassLoader parent;  //父类加载器

    protected ClassLoader(ClassLoader parentLoader) {
        this(parentLoader, false);
    }

    ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
        parent = parentLoader; 
    }
}

大致流程

image

DexPathList

前提:

  • BaseDexClassLoader的参数说明
  • dexElement: 根据":"分隔符, 将dexpath转换成File列表, 记录所有的dexFile
  • nativeLibraryPathElement: 同理, 记录所有的nativeLibrary的File列表
final class DexPathList {
    private Element[] dexElements;
    private final List<File> nativeLibraryDirectories; // 文件对象List, 存储native动态库目录(Directories)
    private final List<File> systemNativeLibraryDirectories; // 文件对象List, 存储系统native动态库目录(Directories)

    final class DexPathList {
    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) { // definingContext 定义上下文
        ...
        this.definingContext = definingContext; // context数据域赋值
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); // 被抑制的异常List
  
        //记录所有的dexFile文件【小节3.2.1】
        this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions); // 可能存在多条路径(使用":"分割), 将多个dex分为一个数组

        //app目录的native库
        this.nativeLibraryDirectories = splitPaths(libraryPath, false); // native库也可能存在多条路径(使用":"分割)
        //系统目录的native库
        this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true); // 同上获取系统native库
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); // 将本地的和系统的native库整合在一起
        //记录所有的Native动态库
        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, suppressedExceptions); // 同上获取Element数组
        ...
    }
}

makePathElements()

在记录dexElements时用到, 继续跟进makeDexElements()

private static Element[] makePathElements(List<File> files, File optimizedDirectory,
        List<IOException> suppressedExceptions) {
    return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
}

makeDexElements()

作用

根据File列表, 创建Element数组

大致流程

  • 创建Elements数组

  • 循环获取File对象

    • 判断是否是目录

      • 如果是目录则创建一个根据File对象创建一个Element对象, 放入elements中
    • 判断是否是文件

      • 获取文件名

      • 创建一个DexFile对象

      • 如果是dex文件

        • 调用loadDexFile()加载dex文件

        • 如果加载成功

          • 根据DexFile对象创建一个Element对象, 放入elements中
      • 如果不是dex文件

        • 调用loadDexFile()加载dex文件

        • 如果加载成功

          • 根据DexFile对象和File对象创建一个Element对象, 放入elements中
        • 如果加载失败

          • 根据File对象创建一个Element对象, 放入elements中
    • 既不是目录, 也不文件

      • 输出错误提示
  • 如果elementPos的长度与预先申请的长度不一致(说明路径中有错误 或者 loadDexFile()发生错误)

    • 使用Arrays.copyof拷贝合适长度的elements返回
  • elements返回

private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
        List<IOException> suppressedExceptions, ClassLoader loader) {
    return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);
}

// 重载方法, 多了一个boolean参数isTrusted, 默认为false
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
        List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
  Element[] elements = new Element[files.size()];  //获取文件个数
  int elementsPos = 0; // 下标
  for (File file : files) {
      if (file.isDirectory()) { // 如果是目录
          elements[elementsPos++] = new Element(file); // 根据File对象, 创建一个Element对象
      } else if (file.isFile()) { // 如果是文件
          String name = file.getName(); // 获取文件名
          DexFile dex = null; // 创建一个DexFile对象
          //匹配以.dex为后缀的文件
          if (name.endsWith(DEX_SUFFIX)) { // 如果是dex文件
              //【小节3.2.3】
              dex = loadDexFile(file, optimizedDirectory, loader, elements); // 调用loadDexFile()加载dex文件
              if (dex != null) { // 若加载成功
                  elements[elementsPos++] = new Element(dex, null); // 将dexFile对象添加到Element数组中
              }
          } else { // 如果不是dex文件
              dex = loadDexFile(file, optimizedDirectory, loader, elements); // 同样调用loadDexFile()加载文件     
              if (dex == null) { // 如果加载失败
                  elements[elementsPos++] = new Element(file); // 则直接添加File对象
              } else {
                  elements[elementsPos++] = new Element(dex, file); // 否则调用另一个构造方法构造Element对象
              }
          }
          if (dex != null && isTrusted) { // 设置dex文件是否可信任
            dex.setTrusted();
          }
      } else { // 既不是目录, 也不是文件, 则输出错误消息
          System.logW("ClassLoader referenced unknown path: " + file);
      }
  }
  if (elementsPos != elements.length) { // 如果elementPos的长度与预先申请的长度不一致(说明路径中有错误)
      elements = Arrays.copyOf(elements, elementsPos); // 则创建合适长度的elements返回
  }

  return elements;
}

loadDexFile()

作用

根据File对象, 创建DexFile对象

private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
                                   Element[] elements)
        throws IOException {
    if (optimizedDirectory == null) { // 如果优化目录为null
        return new DexFile(file, loader, elements);  //创建DexFile对象
    } else { // 如果有优化目录
        String optimizedPath = optimizedPathFor(file, optimizedDirectory);
        return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
    }
}

DexFile

作用

DexFile类

DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)
        throws IOException {
    this(file.getPath(), loader, elements);
}

DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
    // 初始化三个数据域
    mCookie = openDexFile(fileName, null, 0, loader, elements);
    mInternalCookie = mCookie;
    mFileName = fileName;
}

OpenDexFile

作用

打开dex文件, 并返回Object对象

参数说明

  • sourceName: PathClassLoader构造函数传递的dexPath中用":"后的文件名, 就是单个dex文件路径
  • outputName: null
  • flags: 0
  • loader: null
  • elements: 前面的Element数组
private static Object openDexFile(String sourceName, String outputName, int flags,
        ClassLoader loader, DexPathList.Element[] elements) throws IOException {
    //【见小节3.2.6】
    return openDexFileNative(new File(sourceName).getAbsolutePath(),
                             (outputName == null) ? null : new File(outputName).getAbsolutePath(),
                             flags,
                             loader,
                             elements);
}

DexFile_openDexFileNative

static jobject DexFile_openDexFileNative(JNIEnv* env,
                                         jclass,
                                         jstring javaSourceName,
                                         jstring javaOutputName ATTRIBUTE_UNUSED,
                                         jint flags ATTRIBUTE_UNUSED,
                                         jobject class_loader,
                                         jobjectArray dex_elements) {
  ScopedUtfChars sourceName(env, javaSourceName);
  if (sourceName.c_str() == nullptr) {
    return 0;
  }
  Runtime* const runtime = Runtime::Current();
  ClassLinker* linker = runtime->GetClassLinker();
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  std::vector<std::string> error_msgs;
  const OatFile* oat_file = nullptr;

  dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
                                                               class_loader,
                                                               dex_elements,
                                                               /*out*/ &oat_file,
                                                               /*out*/ &error_msgs);

  if (!dex_files.empty()) {
    jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
    ...
    return array;
  } else {
    ...
    return nullptr;
  }
}

初始化结果

PathClassLoader创建完成后, 拥有:

  • 目标程序的文件路径
  • native lib路径
  • 父加载器对象

loadClass

PathClassLoader完成初始化后, 即可开始loadClass(), 该方法继承自ClassLoader类中

ClassLoader

public abstract class ClassLoader {

    public Class<?> loadClass(String className) throws ClassNotFoundException {
        return loadClass(className, false); // 调用重载方法
    }

    // 重载方法
    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        //判断当前类加载器是否已经加载过指定类,若已加载则直接返回
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) { 
            //如果没有加载过,则调用parent的类加载递归加载该类,若已加载则直接返回
            clazz = parent.loadClass(className, false);
      
            if (clazz == null) {
                //还没加载,则调用当前类加载器来加载[见小节3.2]
                clazz = findClass(className);
            }
        }
        return clazz;
    }
}

findLoadedClass()

作用

该加载器中, 判断该类是否已加载

protected final Class<?> findLoadedClass(String name) {
    ClassLoader loader;
    if (this == BootClassLoader.getInstance()) // 如果是BootClassLoader
        loader = null; // loader数据域为null
    else
        loader = this; // 否则为自身
    return VMClassLoader.findLoadedClass(loader, name); // 调用VMClassLoader.findLoadedClass()
}

findClass()

作用

调用了BaseDexClassLoader的pathList.findClass()来找到类

public class BaseDexClassLoader extends ClassLoader {
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class c = pathList.findClass(name, suppressedExceptions);
        ...
        return c;
    }
}

DaxPathList.findClass()

作用

一个ClassLoader可以包含多个dex文件, 每个dex文件被封装到一个Element对象, 循环查询DaxPathList中dex文件的elements数组, 如果找到了该类, 则直接退出, 不再继续遍历.(热修复的核心, 只需要将修复的类打包到dex文件中, 然后插入到之前的类所在dex文件之前, 就会优先加载该dex文件中的类.)

再调用loadClassBinaryName()根据类名称来加载Class

public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) { // dex文件的elements
        DexFile dex = element.dexFile; // 获取DexFile
        if (dex != null) {
            //找到目标类,则直接返回[见小节3.6]
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); // 调用loadClassBinaryName()方法加载类
            if (clazz != null) {
                return clazz;
            }
        }
    }
    return null;
}

DexFile.laodClassBinaryName()

public final class DexFile {

    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, suppressed);
    }

    private static Class defineClass(String name, ClassLoader loader, Object cookie, List<Throwable> suppressed) {
        Class result = null;
	// 调用
        try {
            result = defineClassNative(name, loader, cookie);
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }
}

defineClassNative()

static jclass DexFile_defineClassNative(JNIEnv* env, jclass, jstring javaName, jobject javaLoader,
                                        jobject cookie) {
  std::unique_ptr<std::vector<const DexFile*>> dex_files = ConvertJavaArrayToNative(env, cookie);
  if (dex_files.get() == nullptr) {
    return nullptr; //dex文件为空, 则直接返回
  }

  ScopedUtfChars class_name(env, javaName);
  if (class_name.c_str() == nullptr) {
    return nullptr; //类名为空, 则直接返回
  }

  const std::string descriptor(DotToDescriptor(class_name.c_str()));
  const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str())); //将类名转换为hash码
  for (auto& dex_file : *dex_files) {
    const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str(), hash);
    if (dex_class_def != nullptr) {
      ScopedObjectAccess soa(env);
      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
      class_linker->RegisterDexFile(*dex_file);
      StackHandleScope<1> hs(soa.Self());
      Handle<mirror::ClassLoader> class_loader(
          hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));
      //获取目标类
      mirror::Class* result = class_linker->DefineClass(soa.Self(), descriptor.c_str(), hash,
                                                        class_loader, *dex_file, *dex_class_def);
      if (result != nullptr) {
        // 找到目标对象
        return soa.AddLocalReference<jclass>(result);
      }
    }
  }
  return nullptr; //没有找到目标类
}

总结

各加载器职责

  • PathClassLoader

    • 系统类加载器
    • app类加载器
    • optimizedDirectory为null, 采用默认目录/data/dalvik-cache/
  • DexClassLoader

    • 从包含classes.dex的jar或者apk中, 加载类
    • 可用于执行动态加载,但必须是app私有可写目录来缓存odex文件.
    • 能够加载系统没有安装的apk或者jar文件, 因此很多插件化方案都是采用DexClassLoade
  • BaseDexClassLoader

    • 基础的类加载器, PathClassLoader和DexClassLoader只是在其构造函数上的简单封装
  • BootClassLoader: 父类的构造器

学习文章链接