ClassLoader是什么
参考:Java类加载机制
Android中的类加载器的层次结构
BootClassLoader : 主要用于加载系统的类,包括java和android系统的类库
URLClassLoader: 只能用于加载jar文件,但是由于 dalvik 不能直接识别jar,所以在 Android 中无法使用这个加载器
BaseDexClassLoader:作为PathClassLoader和DexClassLoader的父类,封装了共用的核心业务逻辑
PathClassLoader : 主要用于加载应用内中的类, 它加载的路径是固定的, 因此无法指定
DexClassLoader : 可以用于加载任意路径的zip,jar或者apk文件, 可以实现动态加载
双亲委派模型
参考:Java类加载机制
1 | ClassLoader loader = getClassLoader(); |
输入结果:
Android类加载过程
1、生成Element数组dexElements,生成pathList
BaseDexClassLoader的构造方法如下:1
2
3
4
5public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
BaseDexClassLoader中的构造方法包含4个参数,他们分别是:
(1) dexPath : 指目标类所在的APK或jar文件的路径.类装载器将从该路径中寻找指定的目标类,该类必须是APK或jar的全路径.如果要包含多个路径,路径之间必须使用特定的分割符分隔,分隔符为File.pathSeparator(“:”).
(2) optimizedDirectory : 由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,该参数就是指定解压出的dex文件存放的路径.
(3) libraryPath : 指目标类中所使用的C/C++库存放的路径
(4) parent : 是指该装载器的父装载器
在BaseDexClassLoader中生成DexPathList类的对象pathList,在DexPathList的构造方法中调用DexPathList类中的makePathElements()方法,在makePathElements()方法中会调用splitDexPath()方法,如果dexPath包含多个路径,splitDexPath()方法能够提取出File.pathSeparator分隔的多个路径,并将多个路径放入List中返回,然后makePathElements()方法会调用loadDexFile()方法遍历List中每个包含dex的文件生成DexFile,用生成的DexFile生成Element对象,用这些Element对象构成Element数组dexElements。
DexPathList.java中的loadDexFile()方法源码如下:1
2
3
4
5
6
7
8
9private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}
一个应用程序中所有类加载到内存中,就是在makePathElements执行loadDexFile()方法来实现的。
optimizedDirectory是一个内部存储路径,用来缓存我们需要加载的dex文件的。DexClassLoader可以指定自己的optimizedDirectory,所以它可以加载外部的dex,因为这个dex会被复制到内部路径的optimizedDirectory;而PathClassLoader没有optimizedDirectory,所以它只能加载内部的dex,这些大都是存在系统中已经安装过的apk里面的。
它们的不同之处总结如下是:
DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk;
PathClassLoader只能加载系统中已经安装过的apk,Android中大部分类的加载默认采用此类
2、pathList调用BaseDexClassLoader中的findClass()方法
调用BaseDexClassLoader的findClass()方法,在此方法,pathList调用DexPathList中的findClass()方法,BaseDexClassLoader的findClass()方法如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}
3、调用DexPathList中的findClass()方法
pathList是DexPathList类型的一个对象,DexPathList中的findClass()方法源码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
DexPathList的findClass()方法,遍历dexElements元素的DexFile实例,也就是遍历所有加载过的dex文件,一个个调用loadClassBinaryName()方法,看能不能找到我们想要的类,如果可以,返回这个类。
DexFile.java中的loadClassBinaryName()方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
private static native Class defineClassNative(String name,
ClassLoader loader, Object cookie) throws ClassNotFoundException, NoClassDefFoundError;
4、返回
将加载的类返回ClassLoader类的loadClass()方法。
应用场景
应用分包(MultiDEX)
解决Android方法数限制问题
热更新
在MultiDex基础上,通过Hook技术和反射技术, dexElements数组头部插入新的DEX文件路径, 实现Dex文件的动态替换,从而修复线上BUG。
(这只是热更新的一种方法)
插件化(热部署)
在MultiDex基础上,通过Hook技术和反射技术, dexElements数组动态插入新的DEX文件路径, 实现Dex文件的动态加载,功能的扩展。
(这只是插件化的一种方法)
应用加固
解决壳程序调起原程序的关键。
参考资料
http://www.itwendao.com/article/detail/253115.html
http://www.jianshu.com/p/3afa47e9112e
https://segmentfault.com/a/1190000005113493