相关阅读
插件化知识梳理(1) - Small 框架之如何引入应用插件
插件化知识梳理(2) - Small 框架之如何引入公共库插件
插件化知识梳理(3) - Small 框架之宿主分身
插件化知识梳理(4) - Small 框架之如何实现插件更新
插件化知识梳理(5) - Small 框架之如何不将插件打包到宿主中
插件化知识梳理(6) - Small 源码分析之 Hook 原理
插件化知识梳理(7) - 类的动态加载入门
插件化知识梳理(8) - 类的动态加载源码分析
插件化知识梳理(9) - 资源的动态加载示例及源码分析
插件化知识梳理(10) - Service 插件化实现及原理
一、前言
在 插件化知识梳理(7) - 类的动态加载入门 中,我们通过一个例子来演示了如何通过PathClassLoader
和DexClassLoader
实现类的动态加载。今天这篇文章,我们一起来对这个类加载内部的实现源码进行一次简单的走读。源码的地址为 地址 ,友情提示,需要翻墙。
二、源码解析
整个加载过程设计到的类,如下图所示:
2.1 BaseDexClassLoader
从上面的图中,我们可以看到DexClassLoader
和PathClassLoader
,都是BaseDexClassLoader
的子类,而当我们调用以上两个类的构造方法时,其实都是调用了super()
方法,也就是BaseDexClassLoader
的构造方法,它支持传入一下四个参数:
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());
}
}
-
dexPath
:包含了类和资源的jar/apk
文件,也就是上一篇例子当中的plugin_dex.jar
,如果有多个文件,那么用File.pathSeparator
进行分割,如果我们传入的是jar/apk
文件,那么它会先将里面的.dex
文件解压到内存当中,而如果是.dex
文件,那么将不会有这一过程。 -
optimizedDirectory
:这个参数目前已经被废弃了,没有什么作用。 -
librarySearchPath
:Native
库的路径,同样可以用File.pathSeparator
进行分割。 -
parent
:父加载器的实例。
而DexClassLoader
和PathClassLoader
的区别就在于后者不支持传入optimizedDirectory
这个参数,现在看来,对于最新的源码,这个参数已经被废弃了,那么这两个类其实是一样的。但是具体的实现,还是要看手机的安卓版本。
2.2 DexPathList
在前面的例子当中,获得PathClassLoader/DexClassLoader
实例之后,调用了loadClass
方法,它其实调用的是基类ClassLoader
中的方法:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
//先查看该类是否已经被加载过
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//优先调用父加载器进行加载.
c = parent.loadClass(name, false);
} else {
//2.如果没有父加载器,那么使用 bootstrap 进行加载。
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
//调用 findClass 方法。
long t1 = System.nanoTime();
c = findClass(name);
}
}
return c;
}
对于BaseDexClassLoader
,最终会走到他们重写的findClass
方法,而该方法又会去通过pathList
去寻找,如果找不到,那么就会抛出异常,
@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;
}
并且其它的公有方法,都是通过pathList
去寻找的,因此这个pathList
是如何构成的就是我们分析源码的关键。
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
//dex/resource (class path) elements
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
//application native library directories
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
//system native library directories
this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
//native library path elements
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions = suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}
在上面的构造方法中,最关键的就是通过makeDexElements
和makePathElements
来构建dexElements
和nativeLibraryPathElements
,它们两个分别为Element
和NativeLibraryElement
类型的数组,在 插件化知识梳理(7) - 类的动态加载入门 中,这两个变量的值为:
2.3 makeDexElements
下面,我们来看一下makeDexElements
的内部实现逻辑:
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader) {
Element[] elements = new Element[files.size()];
int elementsPos = 0;
/*
* Open all files and load the (direct or contained) dex files up front.
*/
for (File file : files) {
if (file.isDirectory()) {
// We support directories for looking up resources. Looking up resources in
// directories is useful for running libcore tests.
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) {
String name = file.getName();
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex != null) {
elements[elementsPos++] = new Element(dex, null);
}
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + file, suppressed);
suppressedExceptions.add(suppressed);
}
} else {
DexFile dex = null;
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if
* the zip file turns out to be resource-only (that is, no classes.dex file
* in it).
* Let dex == null and hang on to the exception to add to the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(suppressed);
}
if (dex == null) {
elements[elementsPos++] = new Element(file);
} else {
elements[elementsPos++] = new Element(dex, file);
}
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}
这里面,最终会创建一个和files
相等大小的elements
数组,其最终目的是为每个Element
中的dexFile
赋值,而dexFile
则是通过loadDexFile
方法创建的。
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements) throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file, loader, elements);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
}
}
这里面,会根据optimizedDirectory
的区别,来调用DexFile
不同的函数,我们先看静态方法,可以看到,它里面也是调用了new DexFile
来返回一个DexFile
对象:
static DexFile loadDex(String sourcePathName, String outputPathName,
int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
/*
* TODO: we may want to cache previously-opened DexFile objects.
* The cache would be synchronized with close(). This would help
* us avoid mapping the same DEX more than once when an app
* decided to open it multiple times. In practice this may not
* be a real issue.
*/
return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
}
这里面,又会调用openDex
方法,得到一个mCookie
变量,在前面的例子中,这个mCookie
是一个long
型的对象,对于里面的内部实现,可以参见这篇文章 跟踪源码分析 Android DexClassLoader 加载机制
以上就是整个构建
pathList
的过程,下面,我们来看一下前面所说的DexFileList
中findClass
的过程。
2.4 寻找 Class 的过程
DexFileList
中寻找类的代码如下:
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
数组,调用每个的findClass
方法,直到找到为止,而Element
中的该方法,则会调用在2.3
中创建的DexFile
的loadClassBinaryName
来查找该Class
对象:
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 {
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;
}
三、小结
经过一次简单的源码走读,我们可以知道,DexClassLoader/PathClassLoader
的内部,为每一个传入的jar/apk/dex
文件,都创建了一个Element
变量,它们被保存在DexFileList
当中,而每一个Element
变量中,又包含了一个关键的DexFile
类,之后我们通过DexClassLoader/PathClassLoader
寻找类或者资源时,其实最终都是调用了DexFile
中的Native
方法,如果有兴趣的同学可以去研究这些方法的内部实现。
最后,简单地提一下,在Small
的源码当中,并没有直接使用DexClassLoader/PathClassLoader
,它首先是直接调用了DexFile
的静态方法来为每一个插件创建一个DexFile
:
之后,再通过反射,将这些
DexFile
加入到宿主的ClassLoader
当中,而不是像我们之前那样,为每一个插件都创建一个ClassLoader
。该方法中,会像
DexFileList
中所做的那样,通过makeDexElement
方法,为每一个DexFile
创建一个Element
对象:最后,再将这个对象加入到
pathList
变量中:更多文章,欢迎访问我的 Android 知识梳理系列:
- Android 知识梳理目录:http://www.jianshu.com/p/fd82d18994ce
- 个人主页:http://lizejun.cn
- 个人知识总结目录:http://lizejun.cn/categories/
网友评论