美文网首页
ClassLoader

ClassLoader

作者: Drew_MyINTYRE | 来源:发表于2021-08-21 20:00 被阅读0次

    Classloader 的类型

    Java 中的 ClassLoader 可以加载 jar 文件和 Class 文件(本质是加载 Class 文件),这一点在 Android 中并不适用,因为无论是 DVM 还是 ART ,它们加载的不再是 Class文件,而是 dex 文件,这就需要重新设计 ClassLoader 相关类。

    Android 系统类加载器主要有3种

    • BootClassLoader

    BootClassLoader 是在 Zygote 进程的 Zygote入口方法中被创建的,用于加载 preloaded-classes 文件中存有的预加载类。与 SDK 中的 Bootstrap ClassLoader不同,它并不是由 C/C++代码实现的,而是由 Java 实现的。

     /libcore/ojluni/src/main/java/java/lang/ClassLoader.java
    
    class BootClassLoader extends ClassLoader {
    
        private static BootClassLoader instance;
    
     @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
        public static synchronized BootClassLoader getInstance() {
            if (instance == null) {
                instance = new BootClassLoader();
            }
    
            return instance;
        }
        ...
    }
    

    需要注意的是 BootClassLoader 的访问修饰符是默认的,只有在同一个包中才可以访问,因此我们在应用程序中是无法直接调用的。

    • PathClassLoader

    PathClassLoader 是在 SystemServer 进程中采用工厂模式创建的 。Android 系统使用 PathClassLoader 来加载系统类和应用程序的类,在 PathClassLoader 的构造方法中没有参数 optimizedDirectory。这是因为参数 optimizedDirectory 的 defaultValue/data/dalvik-cache ,因此 PathClassLoader 通常用来加载已经安装的 apkdex 文件(安装的apkdex 文件会存储在 /data/dalvik-cache 中)。

        private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
            if (parsedArgs.invokeWith != null) {
                ...
            } else {
                ClassLoader cl = null;
                if (systemServerClasspath != null) {
                    cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);//1
                    Thread.currentThread().setContextClassLoader(cl);
                }
                return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
            }
        }
    
        static ClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
            String libraryPath = System.getProperty("java.library.path");
    
            return ClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath,
                    ClassLoader.getSystemClassLoader(), targetSdkVersion, true /* isNamespaceShared */,
                    null /* classLoaderName */);
        }
    
        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);
        }
    
    /libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
    
    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);
        }
    }
    
    • DexClassLoader

    DexClassLoader 可以加载 dex 文件以及包含 dex 的压缩文件( apk 和 jar 文件 ),不管加载哪种文件,最终都要加载 dex 文件。

    /libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
    
    // 方法都在 BaseDexClassLoader 中实现
    public class DexClassLoader extends BaseDexClassLoader {
        public DexClassLoader(String dexPath, String optimizedDirectory,
                String librarySearchPath, ClassLoader parent) {
            super(dexPath, null, librarySearchPath, parent);
        }
    }
    

    dexPath -> 加载的 apk 或者 jar 的路径

    optimizedDirectory -> 解压出来的 dex 文件路径 (/data/data/<Package Name>/…)

    librarySearchPath -> dex 加载 so 库的路径

    parent -> 父加载器

    DexPathList 是在 BaseDexClassLoader 的构造方法中创建的 ,里面存储了 dex 相关文件的路径,在 ClassLoader 执行双亲委托模式的查找流程时会从 DexPathList 中进行查找。

    BaseDexClassLoader 继承抽象类 ClassLoaderPathClassLoaderDexClassLoader 都继承 BaseDexClassLoader

    /libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
    
       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();
            }
        }
    
    
     @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;
        }
    
    /libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
    
        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;
        }
    
    // Element 是 DexPathList 的静态内部类 
    
          private final File path;
    
            private final DexFile dexFile;
    
            private ClassPathURLStreamHandler urlHandler;
            private boolean initialized;
    
            public Element(DexFile dexFile, File dexZipPath) {
                this.dexFile = dexFile;
                this.path = dexZipPath;
            }
    
            public Element(DexFile dexFile) {
                this.dexFile = dexFile;
                this.path = null;
            }
    
            public Element(File path) {
              this.path = path;
              this.dexFile = null;
            }
    
      public Class<?> findClass(String name, ClassLoader definingContext,
                    List<Throwable> suppressed) {
                return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                        : null;
            }
    
    //  Element 内部封装了 DexFile ,它用于加载 dex 。如果 DexFile 不为 null 就调用 DexFile 的 loadClassBinaryName 方法:
    
        public 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 {
                // 这是个 Native 方法
                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;
        }
    

    ClassLoader 查找流程

    双亲委托模式?

    所谓双亲委托模式就是类加载器查找 Class ,首先判断该 Class 是否已经加载,如果已经加载过 ,直接获取并返回。如果还没有加载,则委托父加载器去查找这个 Class,这样依次进行递归,直到委托到最顶层的 Bootstrap ClassLoader(Java 的引导类加载器),如果 Bootstrap ClassLoader 找到了该 Class ,就会直接返回,如果没找到,则继续依次向下查找,最后会交由自身去查找。

    采取双亲委托模式主要有如下两点好处:

    • 避免重复加载,如果已经加载过一次 Class ,直接读取,不需要二次加载

    • 更加安全

    如果不使用双亲委托模式,就可以自定义一个 String 类来替代系统的 String 类,这显然会造成安全隐患,采用双亲委托模式会使得系统的 String 类在 Java 虚拟机启动时就被加载,也就无法自定义 String 类来替代系统的 String 类(除非我们修改类加载器搜索类的默认算法)。还有一点, 只有两个类名一致并且被同一个类加载器加载的类, Java 虚拟机才会认为它们是同一个类 ,想要骗过 Java 虚拟机显然不会那么容易。

    小结

    Java 的引导类加载器是由 C++ 编写的, Android 中的引导类加载器则是用 Java 编写的。

    同一个 classname、同一个 packagename、同一个 ClassLoader ,满足这三个条件才被认为是同一个类。

    ClassLoader 加载 Class 的流程

    相关文章

      网友评论

          本文标题:ClassLoader

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