当前位置: 代码迷 >> 综合 >> Android classLoader 从源码理解类加载
  详细解决方案

Android classLoader 从源码理解类加载

热度:25   发布时间:2023-12-17 02:06:53.0

一、概述

我们知道Java中的 ClassLoader可以加载 jar 文件和Class文件(本质时加载Class文件)。在Android中,它们加载到是dex文件。
Android中的ClassLoader类型分别是系统类加载器和自定义加载器。其中系统类加载器主要包括3种,分别是 BootClassLoaderPathClassLoaderDexClassLoader

概览

  • BootClassLoader
    Android 系统启动时会使用 BootClassLoader来预加载常用类。
  • DexClassLoader
    DexClassLoader 可以加载dex文件以及包含dex的压缩文件(apk和jar文件)
  • PathClassLoader
    Android 系统使用PathClassLoader来加载系统类和应用程序的类。
framework 源码使用 android10 release 分支
platform/libcore/dalvik/src/main/java/dalvik/system- PathClassLoader.java- DexClassLoader.java- BaseDexClassLoader.java- DexPathList.java- DexFile.javaplatform/art/runtime/native- dalvik_system_DexFile.ccplatform/ojluni/src/main/java/java/lang/- ClassLoader.java

二、五种类加载构造函数

2.1 PathClassLoader

提供了一个简单的{@link ClassLoader}实现,它对列表进行操作的文件和目录,但没有尝试从网络加载类。Android将这个类用于它的系统类加载器和它的应用程序类加载器。

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
    super(dexPath, null, null, parent);}public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
    super(dexPath, null, librarySearchPath, parent);}@libcore.api.CorePlatformApipublic PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent,ClassLoader[] sharedLibraryLoaders) {
    super(dexPath, librarySearchPath, parent, sharedLibraryLoaders);}
}

2.2 DexClassLoader

一个类装入器,从包含{@code classes.dex}条目的{@code .jar}和{@code .apk}文件中装入类。这可用于执行未作为应用程序的一部分安装的代码。 在API级别26之前,这个类装入器需要一个应用专用的可写目录来缓存优化后的类。使用File dexOutputDir = context.getCodeCacheDir()创建这样一个目录 。不要在外部存储上缓存优化的类。外部存储不提供必要的访问控制来保护应用程序免受代码注入攻击。

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,String librarySearchPath, ClassLoader parent) {
    super(dexPath, null, librarySearchPath, parent);}
}

2.3 BaseDexClassLoader

用于定制如何报告dex文件加载的钩子。

这使框架能够监视dex文件的使用。其目标是简化优化外部dex文件的机制,并允许进一步优化辅助dex文件。只有当BaseDexClassLoader的新实例被构造时,报告才会发生,并且只有在这个字段被设置为{@link BaseDexClassLoader#setReporter}后才会被激活。

public class BaseDexClassLoader extends ClassLoader {
    @UnsupportedAppUsageprivate final DexPathList pathList;public BaseDexClassLoader(String dexPath,String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,boolean isTrusted) {
    super(parent);//在创建路径列表之前设置共享库。ART依赖于类加载器层次结构在加载dex文件之前完成。this.sharedLibraryLoaders = sharedLibraryLoaders == null? null: Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);if (reporter != null) {
    reportClassLoaderChain();}}//设置dex加载通知的报告器。一旦设置完毕,BaseDexClassLoader的所有新实例都将在构建已加载的dex文件时报告。@libcore.api.CorePlatformApipublic static void setReporter(Reporter newReporter) {
    reporter = newReporter;}
}

BaseDexClassLoader构造函数, 有一个非常重要的过程, 那就是初始化DexPathList对象.

另外该构造函数的参数说明:

  • dexPath: 包含目标类或资源的apk/jar列表;当有多个路径则采用:分割;
  • optimizedDirectory: 优化后dex文件存在的目录, 可以为null;
  • libraryPath: native库所在路径列表;当有多个路径则采用:分割;
  • ClassLoader:父类的类加载器.

2.4 ClassLoader

类加载器是负责装入类的对象。类是一个抽象类。给定类的属性,类加载器应该尝试定位或生成构成类定义的数据。应用程序实现的子类是为了扩展Java虚拟机动态加载类的方式。安全管理器通常可以使用类装入器来指示安全域。


public abstract class ClassLoader {
    private ClassLoader parent;  //记录父类加载器protected ClassLoader() {
    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

    static private class SystemClassLoader {
    public static ClassLoader loader = ClassLoader.createSystemClassLoader();}private static ClassLoader createSystemClassLoader() {
    String classPath = System.getProperty("java.class.path", ".");String librarySearchPath = System.getProperty("java.library.path", "");return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());}

2.5 BootClassLoader

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为例来说明类的加载过程,先初始化,然后执行loadClass()方法来加载相应的类。

3.1 PathClassLoader构造方法

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动态库 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; }
}

3.2 DexPathList

类路径条目可以是任何一个:包含可选的顶级{@code classes.dex}文件和任意资源的{@code .dex}文件,或者是普通的{@code .dex}文件(不可能有关联的资源)。这个类还包含使用这些列表查找类和资源的方法

public final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";private static final String zipSeparator = "!/";@UnsupportedAppUsageprivate final ClassLoader definingContext;@UnsupportedAppUsageprivate Element[] dexElements;//dex/资源(类路径)元素的列表。应该被称为pathElements,但是Facebook应用程序使用反射来修改“dexelement”@UnsupportedAppUsageNativeLibraryElement[] nativeLibraryPathElements; //本地库路径元素列表@UnsupportedAppUsageprivate final List<File> nativeLibraryDirectories;//应用程序本机库目录的列表。@UnsupportedAppUsageprivate final List<File> systemNativeLibraryDirectories;@UnsupportedAppUsageprivate IOException[] dexElementsSuppressedExceptions;//创建dexElements列表时抛出的异常。@UnsupportedAppUsagepublic DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory) {
    this(definingContext, dexPath, librarySearchPath, optimizedDirectory, false);}DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
    if (definingContext == null) {
    throw new NullPointerException("definingContext == null");}if (dexPath == null) {
    throw new NullPointerException("dexPath == null");}if (optimizedDirectory != null) {
    if (!optimizedDirectory.exists())  {
    throw new IllegalArgumentException("optimizedDirectory doesn't exist: "+ optimizedDirectory);}if (!(optimizedDirectory.canRead()&& optimizedDirectory.canWrite())) {
    throw new IllegalArgumentException("optimizedDirectory not readable/writable: "+ optimizedDirectory);}}this.definingContext = definingContext;ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();//记录所有的dexFile文件this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext, isTrusted);//app目录的native库 this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);this.systemNativeLibraryDirectories =splitPaths(System.getProperty("java.library.path"), true);//记录所有的Native动态库 this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());if (suppressedExceptions.size() > 0) {
    this.dexElementsSuppressedExceptions =suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);} else {
    dexElementsSuppressedExceptions = null;}}}

DexPathList初始化过程,主要功能是收集以下两个变量信息:

  • dexElements: 根据多路径的分隔符“;”将dexPath转换成File列表,记录所有的dexFile
  • nativeLibraryPathElements: 记录所有的Native动态库, 包括app目录的native库和系统目录的native库。

3.2.1 DexPathList::makePathElements

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

该方法的主要功能是创建Element数组

3.2.2 DexPathList::makeDexElements

        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;//打开所有文件并预先加载(直接或包含的)dex文件。for (File file : files) {
    if (file.isDirectory()) {
    elements[elementsPos++] = new Element(file);} else if (file.isFile()) {
    String name = file.getName();DexFile dex = null;//匹配以.dex为后缀的文件if (name.endsWith(DEX_SUFFIX)) {
    try {
    dex = loadDexFile(file, optimizedDirectory, loader, elements);if (dex != null) {
    elements[elementsPos++] = new Element(dex, null);}} catch (IOException suppressed) {
    System.logE("Unable to load dex file: " + file, suppressed);suppressedExceptions.add(suppressed);}} else {
    try {
    dex = loadDexFile(file, optimizedDirectory, loader, elements);} catch (IOException suppressed) {
    suppressedExceptions.add(suppressed);}if (dex == null) {
    elements[elementsPos++] = new Element(file);} else {
    elements[elementsPos++] = new Element(dex, file);}}if (dex != null && isTrusted) {
    dex.setTrusted();}} else {
    System.logW("ClassLoader referenced unknown path: " + file);}}if (elementsPos != elements.length) {
    elements = Arrays.copyOf(elements, elementsPos);}return elements;}

3.2.3 DexPathList::loadDexFile

    @UnsupportedAppUsageprivate static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,Element[] elements)throws IOException {
    if (optimizedDirectory == null) {
    return new DexFile(file, loader, elements);} else {
    String optimizedPath = optimizedPathFor(file, optimizedDirectory);return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);}}

3.2.4 创建DexFile对象

应用程序不应该直接使用这个类。在大多数情况下,这会损害性能,在最坏的情况下会导致不正确的字节码执行。应用程序应该使用一个标准的类加载器,比如{@link dalvik.system。PathClassLoader}。这个API将在未来的Android版本中被删除。

DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)throws IOException {
    this(file.getPath(), loader, elements);
}private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,DexPathList.Element[] elements) throws IOException {
    if (outputName != null) {
    try {
    String parent = new File(outputName).getParent();if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
    throw new IllegalArgumentException("Optimized data directory " + parent+ " is not owned by the current user. Shared storage cannot protect"+ " your application from code injection attacks.");}} catch (ErrnoException ignored) {
    // assume we'll fail with a more contextual error later}}mCookie = openDexFile(sourceName, outputName, flags, loader, elements);mInternalCookie = mCookie;mFileName = sourceName;}

参数介绍

  • sourceName : 带有class .dex的Jar或APK文件
  • outputName : 保存DEX数据的优化格式的文件。
  • elements : makeDexElements()过程生成的临时Element数组;

3.2.5 DexFile::openDexFile

    private static Object openDexFile(String sourceName, String outputName, int flags,ClassLoader loader, DexPathList.Element[] elements) throws IOException {
    // Use absolute paths to enable the use of relative paths when testing on host.return openDexFileNative(new File(sourceName).getAbsolutePath(),(outputName == null)? null: new File(outputName).getAbsolutePath(),flags,loader,elements);}

3.2.6 dalvik_system_DexFile.cc::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 nullptr;}std::vector<std::string> error_msgs;const OatFile* oat_file = nullptr;std::vector<std::unique_ptr<const DexFile>> dex_files =Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),class_loader,dex_elements,/*out*/ &oat_file,/*out*/ &error_msgs);return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);
}

PathClassLoader创建完成后,就已经拥有了目标程序的文件路径,native lib路径,以及parent类加载器对象。接下来开始执行loadClass()来加载相应的类。

3.3 ClassLoader::loadClass

   public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);}protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{
    // First, check if the class has already been loadedClass<?> 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的类加载递归加载该类,检测是否加载,若已加载则直接返回,否则继续执行;
  • 调用当前类加载器,通过findClass加载

3.3 ClassLoader::findLoadedClass

    protected final Class<?> findLoadedClass(String name) {
    ClassLoader loader;if (this == BootClassLoader.getInstance())loader = null;elseloader = this;return VMClassLoader.findLoadedClass(loader, name);}

3.4 BaseDexClassLoader:: findClass

    @Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {
    ...List<Throwable> suppressedExceptions = new ArrayList<Throwable>();Class c = pathList.findClass(name, suppressedExceptions);...return c;}

3.5 DexPathList.findClass

在此实例指向的dex文件中查找已命名类。这将在最早列出的path元素中找到一个。如果找到了类,但还没有定义,则此方法将在构造此实例时使用的定义上下文中定义它。

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

这里是核心逻辑,一个ClassLoader可以包含多个dex文件,每个dex文件被封装到一个Element对象,这些Element对象排列成有序的数组 dexElements 。当查找某个类时,会遍历所有的dex文件,如果找到则直接返回,不再继续遍历dexElements。也就是说当两个类不同的dex中出现,会优先处理排在前面的dex文件,这便是热修复的核心,将需要修复的类所打包的dex文件插入到dexElements前面。

DexFile::loadClassBinaryName

element.findClass 最后走到 dexFile.loadClassBinaryName

   @UnsupportedAppUsagepublic Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
    return defineClass(name, loader, mCookie, this, suppressed);}private static Class defineClass(String name, ClassLoader loader, Object cookie,DexFile dexFile, List<Throwable> suppressed) {
    Class result = null;try {
    result = defineClassNative(name, loader, cookie, dexFile);} catch (NoClassDefFoundError e) {
    if (suppressed != null) {
    suppressed.add(e);}} catch (ClassNotFoundException e) {
    if (suppressed != null) {
    suppressed.add(e);}}return result;}

dalvik_system_DexFile.cc:: defineClassNative

static jclass DexFile_defineClassNative(JNIEnv* env,jclass,jstring javaName,jobject javaLoader,jobject cookie,jobject dexFile) {
    std::vector<const DexFile*> dex_files;const OatFile* oat_file;if (!ConvertJavaArrayToDexFiles(env, cookie, /*out*/ dex_files, /*out*/ oat_file)) {
    VLOG(class_linker) << "Failed to find dex_file";DCHECK(env->ExceptionCheck());return nullptr;  //dex文件为空, 则直接返回}ScopedUtfChars class_name(env, javaName);if (class_name.c_str() == nullptr) {
    VLOG(class_linker) << "Failed to find class_name";return nullptr;  //类名为空, 则直接返回}const std::string descriptor(DotToDescriptor(class_name.c_str()));const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str()));for (auto& dex_file : dex_files) {
    const dex::ClassDef* dex_class_def =OatDexFile::FindClassDef(*dex_file, descriptor.c_str(), hash); //将类名转换为hash码if (dex_class_def != nullptr) {
    ScopedObjectAccess soa(env);ClassLinker* class_linker = Runtime::Current()->GetClassLinker();StackHandleScope<1> hs(soa.Self());Handle<mirror::ClassLoader> class_loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(javaLoader)));ObjPtr<mirror::DexCache> dex_cache =class_linker->RegisterDexFile(*dex_file, class_loader.Get());if (dex_cache == nullptr) {
    // OOME or InternalError (dexFile already registered with a different class loader).soa.Self()->AssertPendingException();return nullptr;}ObjPtr<mirror::Class> result = class_linker->DefineClass(soa.Self(),descriptor.c_str(),hash,class_loader,*dex_file,*dex_class_def);
//添加使用过的dex文件。这只对DexFile是必需的。因为通常的类加载器已经保持它们的dex文件的活动。>InsertDexFileInToClassLoader(soa.Decode<mirror::Object>(dexFile),class_loader.Get());if (result != nullptr) {
    // 找到目标对象return soa.AddLocalReference<jclass>(result);}}}return nullptr;
}

在native层创建目标类的对象并添加到虚拟机列表。

总结

在这里插入图片描述

这篇文章我们要理解Android常用的classLoader,以及通过将补丁dex文件插入到替换dex文件的Elements的热修复原理。

可以尝试着回答以下问题来巩固

  • 1.虚拟机如何加载这些class文件?
  • 2.Class文件中的信息进入到虚拟机后会发生什么变化?
  • 3.如何打破双亲委派模型

参考 :
http://gityuan.com/2017/03/19/android-classloader/

  相关解决方案