一般说起 PathClassLoader 和 DexClassLoader ,大家都会说,前者只能加载内存中已经安装的apk中的dex,而后者可以加载sd卡中的apk/jar ,因此 DexClassLoader 是热修复和插件化的基础。但是具体为什么DexClassLoader能加载sd卡中的类?
1.PathClassLoader&DexClassLoader
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
//对比发现两者都是继承自BaseDexClassLoader,唯一的不同是DexClassLoader多传了一个optimizedDirectory dex的优化目录。
2.BaseDexClassLoader
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
//1.仅仅初始化了一个变量
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
//2.BaseDexClassLoader查找类的方法,实际上内部调用的是pathList.findClass方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
//3.从pathList的Element数组中找类,找不到就报ClassNotFoundException
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
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
//...略
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
//DexPathList 通过 makeDexElements 得到了一个 Element[]类型的
//dexElements对象数数组里面存放了app的所有dex相关信息。
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions,definingContext);
//...略
}
// files是一个ArrayList<File>列表,它对应的就是apk/dex/jar文件,因为我们可以指定多个文件。
// optimizedDirectory是前面传入dex的输出路径
// suppressedExceptions为一个异常列表
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions) {
ArrayList<Element> elements = new ArrayList<Element>();
/*
* Open all files and load the (direct or contained) dex files
* up front.
*/
for (File file : files) {
File zip = null;
DexFile dex = null;
String name = file.getName();
// 如果是一个dex文件
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
//这里调用了loadDexFile方法
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
// 如果是一个apk或者jar或者zip文件
} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
|| name.endsWith(ZIP_SUFFIX)) {
zip = file;
try {
dex = loadDexFile(file, optimizedDirectory);
} 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);
}
} else if (file.isDirectory()) {
// We support directories for looking up resources.
// This is only useful for running libcore tests.
elements.add(new Element(file, true, null, null));
} else {
System.logW("Unknown file type for: " + file);
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
Element[] elements)
throws IOException {
if (optimizedDirectory == null) {
//1.根据 optimizedDirectory 参数是否为空,执行的方法不同,
//PathClassLoader 执行的是 new DexFile(),
// 而 DexClassLoader 执行的是 DexFile.loadDex() ,
//然而 loadDex 最终也还是会调用 new DexFile() 来创建实例。
return new DexFile(file, loader, elements);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
}
}
//每个Element对象里面包含一个DexFile对象成员,它对应的就是dex文件。
static class Element {
private final File file;
private final boolean isDirectory;
private final File zip;
private final DexFile dexFile;
......
}
4.DexFile
static DexFile loadDex(String sourcePathName, String outputPathName,
int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
//最终都会执行new DexFile()创建对象
return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
}
private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,DexPathList.Element[] elements) throws IOException {
mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
mFileName = sourceName;
}
private static Object openDexFile(String sourceName, String outputName, int flags,
ClassLoader loader, DexPathList.Element[] elements) throws IOException {
return openDexFileNative(new File(sourceName).getAbsolutePath(),
(outputName == null)
? null
: new File(outputName).getAbsolutePath(),
flags,
loader,
elements);
}
//真正的加载逻辑是在openDexFileNative内部,也是在native层实现了dex优化为odex的工作耗时
private static native Object openDexFileNative(String sourceName, String outputName, int flags,
ClassLoader loader, DexPathList.Element[] elements);
5.Native层源码跟踪:
static jint DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
//...略
const DexFile* dex_file;
// 如果outputName为空,则dex_file由sourceName确定
if (outputName.c_str() == NULL) {
dex_file = linker->FindDexFileInOatFileFromDexLocation(dex_location, dex_location_checksum);
} else {
//如果outputName不为空,则在outputName目录中去寻找dex_file
std::string oat_location(outputName.c_str());
dex_file = linker->FindOrCreateOatFileForDexLocation(dex_location, dex_location_checksum, oat_location);
}
//...略
return static_cast<jint>(reinterpret_cast<uintptr_t>(dex_file));
}
判断传入的 outputName 是否为空,分别执行不同的方法,这个 outputName 就是 BaseDexClassLoader 构造方法中传入的 optimizedDirectory 参数, 当 outputName 不为空时【DexClassLoader】 执行FindOrCreateOatFileForDexLocation函数,通过 outputName拿到 oat_location即apk优化后的目录odex文件 ,然后尝试调用 FindDexFileInOatLocation 从 oat_location 中寻找到 dex ,这就是我们经常用到到热修复的原理了,通过在sd卡中存放新的补丁dex/jar/apk替代旧的,来实现更新。
`
const DexFile* ClassLinker::FindOrCreateOatFileForDexLocation(const std::string& dex_location,uint32_t dex_location_checksum,const std::string& oat_location) {
WriterMutexLock mu(Thread::Current(), dex_lock_); // 互锁
return FindOrCreateOatFileForDexLocationLocked(dex_location, dex_location_checksum, oat_location);
}
const DexFile* ClassLinker::FindOrCreateOatFileForDexLocationLocked(const std::string& dex_location,uint32_t dex_location_checksum,const std::string& oat_location) {
const DexFile* dex_file = FindDexFileInOatLocation(dex_location,dex_location_checksum,oat_location);
if (dex_file != NULL) {
// 如果顺利打开,则返回
return dex_file;
}
const OatFile* oat_file = OatFile::Open(oat_location, oat_location, NULL,!Runtime::Current()->IsCompiler());
if (oat_file == NULL) {
return NULL;
}
const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location, &dex_location_checksum);
if (oat_dex_file == NULL) {
return NULL;
}
const DexFile* result = oat_dex_file->OpenDexFile();
return result;
}
当 outputName 为空时【PathClassLoader】 执行 FindDexFileInOatFileFromDexLocation 函数,从 dex_location 中拿到 dex 文件,这个 dex_location 也就是 BaseDexClassLoader 的 dexPath 参数中分割出来的某个存放文件的路径。在 Android 中,系统使用 PathClassLoader 来加载apk中的dex存放到Element数组中,因此apk中的classes.dex都是通过它来加载的。
概述
DexPathList.pngAndroid 中,apk 安装时,系统会使用 PathClassLoader 来加载apk文件中的dex,PathClassLoader的构造方法中,调用父类的构造方法,实例化出一个 DexPathList ,DexPathList 通过 makePathElements 在所有传入的dexPath 路径中,找到DexFile,存入 Element 数组,在应用启动后,所有的类都在 Element 数组中寻找,不会再次加载。
在热更新时,实现 DexClassLoader 子类,传入要更新的dex/apk/jar补丁文件路径(如sd卡路径中存放的patch.jar),通过反射拿到 DexPathList,得到补丁 Element 数组,再从Apk原本安装时使用的 PathClassLoader 中拿到旧版本的 Element 数组,合并新旧数组,将补丁放在数组最前面,这样一个类一旦在补丁 Element 中找到,就不会再次加载,这样就能替换旧 Element 中的旧类,实现热更新.
hotfix
网友评论