背景
在Android开发过程中,大家对于动态加载代码一定不陌生。在应用各个开源框架的过程中多多少少有接触,其主要原理离不开ClassLoader相关的类。这里,我们会从Android中ClassLoader相关类的源码入手,更好的理解和学习动态加载类的原理。
类源码解析
在平时我们做类动态加载的时候,会使用到DexClassLoader
这个类,直接从zip包或者apk包或者直接加载dex文件,然后调用loadClass
方法来获得外部加载的类,使用方法大致如下:
File apkFile = new File(getFilesDir(), "loader.apk");
DexClassLoader dexClassLoader = new DexClassLoader(
apkFile.getAbsolutePath(), getCodeCacheDir().getAbsolutePath(), null, getClassLoader());
Class clazz = dexClassLoader.loadClass("com.codetend.plugin.MainActivity");
那么我们从这个类入手,查看源码:
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
我们可以看到这个类十分简单,其功能实现基本都交给了父类BaseDexClassLoader
。这个类相对内容比较多,我们从构造方法开始看起:
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(parent);
//这里源码是Android8.0的,最后一个传参为null。意味着DexClassLoader与PathClassLoader已经没有区别了
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
if (reporter != null) {
reporter.report(this.pathList.getDexPaths());
}
}
在BaseDexClassLoader
构造的时候,新建了一个DexPathList
的对象,把我们给传入的参数全部透传进去了。这个DexPathList
是十分重要的一个类。同样,从构造方法开始查看源码:
public DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory) {
//检查传参判空
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
// 崩溃栈保存
ArrayList suppressedExceptions = new ArrayList();
// 根据传入的dex路径,保存成Element数组
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext);
// 传入的library路径,加上系统的library路径,构建File list
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
List allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
// 根据File list,保存成NativeLibraryElement数组
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}
构建该类的基础,就是生成两种数组:ELement
和NativeLibraryElement
。从字面上的意思,很容易能猜到这两个数组分别代表了dex元素和so库元素。事实也正如我们所猜想。这里我们先只查看dex元素的构建。从方法makeDexElements
查看:
private static Element[] makeDexElements(List files, File optimizedDirectory,
List suppressedExceptions, ClassLoader loader) {
Element[] elements = new Element[files.size()];
int elementsPos = 0;
for (File file : files) {
if (file.isDirectory()) {
//如果file是文件夹,那么可以用于寻找资源(可暂时忽略,和类加载无关)
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) {
String name = file.getName();
if (name.endsWith(DEX_SUFFIX)) {
//如果是纯粹的dex文件,则生成无dexZipPath的Element
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 {
//如果是apk或者zip包(内含dex文件),则生成有dexZipPath(保存zip包路径)的Element
DexFile dex = null;
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
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;
}