浅析 PathClassLoader 和 DexClassLoader 的区别
一、概述
在 Android 中接触最多的 ClassLoader 是 PathClassLoader 和 DexClassLoader,那么它们之间有什么区别呢?
结论:
Android 8.0 之前:
相同点:PathClassLoader
和DexClassLoader
都能加载外部的dex、jar、apk
。差异: PathClassLoader 和 DexClassLoader 是否能指定 optimizedDirectory 路径 (即 dex2oat 的产物
.odex 存放的位置
)。
- DexClassLoader 可以指定 optimizedDirectory (即 dex2oat 的产物
.odex 存放的位置
),- PathClassLoader 只能使用系统默认位置 (即
/data/dalvik-cache/xxx.odex
)。
Android 8.0 之后:
PathClassLoader
和DexClassLoader
无差异,都能加载外部的dex、jar、apk
。
版本: Android 5.0
由于 Android 8.0 后两者无差异,为了分析两者的差异,我们基于 Android 5.0 代码来分析。
在线源码 https://www.androidos.net.cn/sourcecode
二、 PathClassLoader 和 DexClassLoader 构造方法的区别
由于 PathClassLoader 和 DexClassLoader 类加载器都是继承自 BaseDexClassLoader ,所以在分析 PathClassLoader 和 DexClassLoader 之前先来看一下 BaseDexClassLoader 构造方法中几个参数的含义。
BaseDexClassLoader
BaseDexClassLoader
构造方法中四个参数的含义:
dexPath:需要加载的文件列表 (文件可以是包含了 classes 和 resources 的 JAR、APK),多个文件用 “:
” 分割。
optimizedDirectory:dex 经过 dex2oat 优化后,生成 .odex 文件的存放路径,可以为 null 。
libraryPath:存放需要加载的 native 库 (即.so库
)的目录。
parent:父 ClassLoader。
通过构造函数我们大概可以了解到 BaseDexClassLoader 的运行方式,传入 dex 文件,然后进行优化,保存优化后的 dex 文件到 optimizedDirectory 目录。
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
// 这里将 optimizedDirectory 传入了 DexPathList 中。
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
}
下面来分别看下 DexClassLoader 和 PathClassLoader 构造方案的差异。
DexClassLoader
由 DexClassLoader 类的注释我们可以看出,DexClassLoader 可以执行没有经过安装的应用程序代码。
/**
* A class loader that loads classes from {@code .jar} and {@code .apk} files
* containing a {@code classes.dex} entry. This can be used to execute code not
* installed as part of an application.
*/
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
// 此处将 optimizedDirectory 传入父类构造器中。
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
PathClassLoader
由 PathClassLoader 类的注释我们可以看出,它用来操作文件系统上的一系列文件和目录 的 ClassLoader 实现,但并未说明只能加载已安装应用的代码文件。
/**
* Provides a simple {@link ClassLoader} implementation that operates on a list
* of files and directories in the local file system, but does not attempt to
* load classes from the network. Android uses this class for its system class
* loader and for its application class loader(s).
*/
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) {
// // 此处第二个参数代表 optimizedDirectory,传入null,使用默认的路径(即系统指定的路径)。
super(dexPath, null, libraryPath, parent);
}
}
小结:
经过上面部分,我们知道 DexClassLoader 和 PathClassLoader 的差异在于构造 ClassLoader 对象时,是否给父类 (
BaseDexClassLoader
) 传递 optimizedDirectory 参数 (即是否指定优化后的 .odex 文件的存储路径)。
三、optimizedDirectory 参数的使用
在上面我们分析了 PathClassLoader 和 DexClassLoader 的区别在于参数 optimizedDirectory 是否赋值,接下去我们分析一下optimizedDirectory 参数的使用流程。
1. DexPathList 中的流程
在 BaseDexClassLoader 构造方法中,我们知道 optimizedDirectory 参数传递给了 DexPathList 对象,下面我们来分析一下 DexPathList。
DexPathList
optimizedDirectory 参数在 DexPathList 中传递流程如下:
DexPathList 构造 -> makeDexElements -> loadDexFile
final class DexPathList {
// 省略代码
// 保存 dex 和资源列表
private final Element[] dexElements;
// 保存 .so 库
private final File[] nativeLibraryDirectories;
// 省略代码
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
// 省略代码
// 这里将 optimizedDirectory 传递给了 makeDexElements 方法。
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);
// 省略代码
}
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions) {
// 将 optimizedDirectory 参数传递给 loadDexFile 方法
DexFile dex = loadDexFile(file, optimizedDirectory);
// 省略代码
}
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
// optimizedDirectory 如果不存在,直接创建 DexFile 对象。
return new DexFile(file);
} else {
// 将dex/jar文件路径和输出目录转换为关联的优化dex文件的输出文件路径。
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
// 将优化后的文件路径传入 DexFile 中。
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}
}
小结:
DexPathList 中,optimizedDirectory 参数只是在各个方法中做了透传,最终传递给了 DexFile 对象。
2. DexFile 中的流程
DexFile
optimizedDirectory 参数在 DexFile 中传递流程如下:
DexFile 构造 -> openDexFile -> openDexFileNative
public final class DexFile {
static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) throws IOException {
// 构建 DexFile 对象,并传递 outputPathName 参数。
return new DexFile(sourcePathName, outputPathName, flags);
}
public DexFile(File file) throws IOException {
this(file.getPath());
}
public DexFile(String fileName) throws IOException {
mCookie = openDexFile(fileName, null, 0);
mFileName = fileName;
guard.open("close");
}
private DexFile(String sourceName, String outputName, int flags) throws IOException {
if (outputName != null) {
// 省略代码
}
// 打开 dexFile 文件
mCookie = openDexFile(sourceName, outputName, flags);
mFileName = sourceName;
guard.open("close");
}
private static long openDexFile(String sourceName, String outputName, int flags) throws IOException {
// 最终会执行到 Native 代码逻辑。
return openDexFileNative(new File(sourceName).getAbsolutePath(),
(outputName == null) ? null : new File(outputName).getAbsolutePath(),
flags);
}
private static native long openDexFileNative(String sourceName, String outputName, int flags);
}
小结:
创建 DexFile 对象的方式有两种:
- optimizedDirectory 参数为空时,直接创建 DexFile 对象。
- optimizedDirectory 参数不为空时,通过 DexFile.loadDex() 方法创建 DexFile 对象,并将 optimizedDirectory 转换后的参数传入 DexFile 。
3. Native 层的流程
在 DexFile 中,最终会执行 openDexFileNative 方法调用对应的 native 逻辑。对应的 Native 逻辑为 dalvik_system_DexFile.cc 的 DexFile_openDexFileNative 方法。
ClassLinker->OpenDexFilesFromOat 逻辑在 class_linker.cc 中。
https://www.androidos.net.cn/android/5.0.1_r1/xref/art/runtime/utils.cc
在 DexFile_openDexFileNative 里主要做事情是处理 DEX 文件,并生成 .odex 文件到 optimizedDirectory 里。
dalvik_system_DexFile.cc
static jlong DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
// 省略代码
NullableScopedUtfChars outputName(env, javaOutputName);
if (env->ExceptionCheck()) {
return 0;
}
ClassLinker* linker = Runtime::Current()->GetClassLinker();
std::unique_ptr<std::vector<const DexFile*>> dex_files(new std::vector<const DexFile*>());
std::vector<std::string> error_msgs;
// ClassLinker->OpenDexFilesFromOat 逻辑在 class_linker.cc 中
bool success = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs,
dex_files.get());
// 省略代码
}
class_linker.cc
// oat_location 就是我们从 java 层传递进来的 optimizedDirectory 参数。
bool ClassLinker::OpenDexFilesFromOat(const char* dex_location, const char* oat_location,
std::vector<std::string>* error_msgs,
std::vector<const DexFile*>* dex_files) {
// 省略代码
// Look in cache location if no oat_location is given.
std::string cache_location;
// oat_location 就是我们传入的 optimizedDirectory 参数。为空时就使用默认缓存的路径。
if (oat_location == nullptr) {
// Use the dalvik cache.
// GetInstructionSetString方法在 https://www.androidos.net.cn/android/5.0.1_r1/xref/art/runtime/utils.cc 中。
const std::string dalvik_cache(GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA)));
cache_location = GetDalvikCacheFilenameOrDie(dex_location, dalvik_cache.c_str());
// 将默认的路径赋值给 oat_location。
oat_location = cache_location.c_str();
}
// 省略代码
if (Runtime::Current()->IsDex2OatEnabled() && has_flock && scoped_flock.HasFile()) {
// Create the oat file.
// 创建 OAT 文件到指定的路径下 (即 oat_location)。
open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(),
oat_location, error_msgs));
}
}
utils.cc
(https://www.androidos.net.cn/android/5.0.1_r1/xref/art/runtime/utils.cc)
该方法返回默认的路径:
/data/dalvik-cache/
std::string GetDalvikCacheOrDie(const char* subdir, const bool create_if_absent) {
CHECK(subdir != nullptr);
const char* android_data = GetAndroidData();
const std::string dalvik_cache_root(StringPrintf("%s/dalvik-cache/", android_data));
const std::string dalvik_cache = dalvik_cache_root + subdir;
if (create_if_absent && !OS::DirectoryExists(dalvik_cache.c_str())) {
// Don't create the system's /data/dalvik-cache/... because it needs special permissions.
if (strcmp(android_data, "/data") != 0) {
int result = mkdir(dalvik_cache_root.c_str(), 0700);
if (result != 0 && errno != EEXIST) {
PLOG(FATAL) << "Failed to create dalvik-cache directory " << dalvik_cache_root;
return "";
}
result = mkdir(dalvik_cache.c_str(), 0700);
if (result != 0) {
PLOG(FATAL) << "Failed to create dalvik-cache directory " << dalvik_cache;
return "";
}
} else {
LOG(FATAL) << "Failed to find dalvik-cache directory " << dalvik_cache;
return "";
}
}
return dalvik_cache;
}
4. 小结
optimizedDirectory 参数的传递流程如下:
四、Android 8.0 的版本
BaseDexClassLoader
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent, boolean isTrusted) {
super(parent);
// optimizedDirectory 参数没有传递给 DexPathList。
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
if (reporter != null) {
reportClassLoaderChain();
}
}