ClassLoader

2021-08-21  本文已影响0人  Drew_MyINTYRE

Classloader 的类型

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

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

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 是在 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 可以加载 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 ,就会直接返回,如果没找到,则继续依次向下查找,最后会交由自身去查找。

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

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

小结

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

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

ClassLoader 加载 Class 的流程

上一篇 下一篇

猜你喜欢

热点阅读