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
通常用来加载已经安装的apk
的dex
文件(安装的apk
的dex
文件会存储在/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
继承抽象类 ClassLoader
,PathClassLoader
和DexClassLoader
都继承 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
的流程
网友评论