美文网首页
Android ClassLoader初识

Android ClassLoader初识

作者: 一个追寻者的故事 | 来源:发表于2020-05-08 15:39 被阅读0次
    概述

    一个ClassLoader对象负责加载类。ClassLoader是一个抽象类。当给定一个类的二进制名称时,ClassLoader会尝试 查找生成 构成该类定义的数据(Class对象)。一种典型的策略是将 类名 转换成文件名,然后从文件系统中读取该名称的类文件。

    每一个Class对象都包含一个定义它的ClassLoader对象。 Class#getClassLoader()
    数组类的类对象不是被ClassLoader创建,而是根据Java运行时的要求自动创建的。数组类的ClassLoader和其元素类型的ClassLoader相同,如果元素类型是原始类型,则数组类没有ClassLoader。

    ClassLoader类使用委托模型搜索 类和资源,每一个ClassLoader对象都有一个关联的父ClassLoader。当查找 类和资源 时,ClassLoader实例首先会将对 类和资源 的搜索委托给其 父ClassLoader,然后再尝试自己查找 类和资源。BootClassLoader是虚拟机内置的ClassLoader,本身没有 父ClassLoader,可以作为其他ClassLoader实例的 parent。

    通常情况下,Java虚拟机会以平台相关的方式从本地文件系统加载类,但是有 不是源自文件的,可能有其它来源,例如网络,也可能由应用程序构造。ClassLoader#defineClass 可以将字节数组转换成Class对象。

    类的二进制名称:
    java.lang.Strinng
    javax.swing.JSpinner$DefaultEditor
    java.net.URLClassLoader$3$1

    ClassLoaderUML类图
    ClassLoader家族UML图
    ClassLoader代码
    public abstract class ClassLoader {
        ...
        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 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;
        }
        /**
         * Finds the class with the specified <a href="#name">binary name</a>.
         * This method should be overridden by class loader implementations that
         * follow the delegation model for loading classes, and will be invoked by
         * the {@link #loadClass <tt>loadClass</tt>} method after checking the
         * parent class loader for the requested class.  The default implementation
         * throws a <tt>ClassNotFoundException</tt>.
         *
         * @param  name
         *         The <a href="#name">binary name</a> of the class
         *
         * @return  The resulting <tt>Class</tt> object
         *
         * @throws  ClassNotFoundException
         *          If the class could not be found
         *
         * @since  1.2
         */
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            throw new ClassNotFoundException(name);
        }
        ...
    }
    

    重点看 loadClass 的方法实现,使用了 Template Method 设计模式。定义了查找类的主体算法逻辑, findClass 可以不同具体实现。例如可以从文件、网络、内存中寻找。loadClass 主体逻辑很简单:

    1、查询 会否已加载(从自己ClassLoader中查找),如果已加载直接返回。
    2、如果未加载,委托给 父ClassLoader 去查询。父ClassLoader又会 执行一遍同样的 loadClass 逻辑
    3、如果从来都没有加载过,会执行 findClass去创建 Class 对象。

    BaseDexClassLoader代码

    是一个基于dex的 一个常用功能的ClassLoader实现的 基类
    还是看原文吧,怎么翻译都很怪异。
    Base class for common functionality between various dex-based ClassLoader implementations.

    public class BaseDexClassLoader extends ClassLoader {
        ...
    
        private final DexPathList pathList;
    
        /**
         * Constructs an instance.
         * Note that all the *.jar and *.apk files from {@code dexPath} might be
         * first extracted in-memory before the code is loaded. This can be avoided
         * by passing raw dex files (*.dex) in the {@code dexPath}.
         *
         * @param dexPath the list of jar/apk files containing classes and
         * resources, delimited by {@code File.pathSeparator}, which
         * defaults to {@code ":"} on Android.
         * @param optimizedDirectory this parameter is deprecated and has no effect
         * @param librarySearchPath the list of directories containing native
         * libraries, delimited by {@code File.pathSeparator}; may be
         * {@code null}
         * @param parent the parent class loader
         */
        public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                String librarySearchPath, ClassLoader parent) {
            super(parent);
            this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
    
            if (reporter != null) {
                reporter.report(this.pathList.getDexPaths());
            }
        }
    
        //dexFile must be an in-memory representation of a full dexFile.
        public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
            // TODO We should support giving this a library search path maybe.
            super(parent);
            this.pathList = new DexPathList(this, dexFiles);
        }
    
        @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;
        }
        ...
    }
    

    BaseDexClassLoader#findClass 的最终的实现是 DexPathList #findClass

    DexPathList代码
    final class DexPathList {
        ...
        private final ClassLoader definingContext;
        /**
         * List of dex/resource (class path) elements.
         * Should be called pathElements, but the Facebook app uses reflection
         * to modify 'dexElements' (http://b/7726934).
         */
        private Element[] dexElements;
    
        /** List of native library path elements. */
        private final NativeLibraryElement[] nativeLibraryPathElements;
    
        /** List of application native library directories. */
        private final List<File> nativeLibraryDirectories;
    
        /** List of system native library directories. */
        private final List<File> systemNativeLibraryDirectories;
    
        //重点
        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;
        }
    }
    
    static class Element {
            /**
             * A file denoting a zip file (in case of a resource jar or a dex jar), or a directory
             * (only when dexFile is null).
             */
            private final File path;
    
            private final DexFile dexFile;
    
            private ClassPathURLStreamHandler urlHandler;
            private boolean initialized;
            ...
            public Class<?> findClass(String name, ClassLoader definingContext,
                    List<Throwable> suppressed) {
                return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                        : null;
            }
          ...
    }
    

    DexPathList 中维护了一个数组 Element[] dexElements ,这个数组是一个dex资源的集合。(注意看dexElements 的注释,很有意思) findClass的时候,遍历 dexElements寻找Class,如果找到就返回,否则继续循环,如果最终没找到哦,则返回null。也就是说同一个类,如果两个dex文件都包含,则先取到哪个dex文件,就从这个文件中装载,热修复就可以从这里入手,修复异常的java类

    其它

    PathClassLoader 和 DexClassLoader 没有具体逻辑实现,没看出有什么区别。
    加载系统framework的类使用的 BootClassLoader、加载应用自己编写的类用的 PathClassLoader

    Log.e("MainActivity", "" + String.class.getClassLoader());
    Log.e("MainActivity", "" + MainActivity.class.getClassLoader());
    
    2020-05-08 15:36:02.782 31478-31478/com.jxf.androidhotfix E/MainActivity: java.lang.BootClassLoader@8046010
    2020-05-08 15:36:02.783 31478-31478/com.jxf.androidhotfix E/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/sdcard/patch.jar", zip file "/data/app/com.test.androidhotfix-da67YM0aVlq3wyo_3rK_rw==/base.apk"],nativeLibraryDirectories=[/data/app/com.test.androidhotfix-da67YM0aVlq3wyo_3rK_rw==/lib/arm64, /system/lib64, /system/vendor/lib64]]]
    

    相关文章

      网友评论

          本文标题:Android ClassLoader初识

          本文链接:https://www.haomeiwen.com/subject/vbaxnhtx.html