相关源码地址:
http://aospxref.com/android-5.0.1_r1/xref/art/runtime/native/dalvik_system_DexFile.cc
http://aospxref.com/android-5.0.1_r1/xref/art/runtime/class_linker.cc
http://aospxref.com/android-5.0.1_r1/xref/art/runtime/dex_file.cc
分析涉及到几个主要的方法:
1、dalvik_system_DexFile.DexFile_openDexFileNative
2、class_linker.OpenDexFilesFromOat
3、class_linker.FindOpenedOatDexFile
4、class_linker.LoadMultiDexFilesFromOatFile
5、class_linker.CreateOatFileForDexLocation
6、oat_file.OpenDexFile
7、dex_file.Open
8、dex_file.OpenZip
9、dex_file.OpenFromZip
通过对Java层的源码分析, 其实并没有得到很有用的信息, 针对DexClassLoader与PathClassLoader的区别还是比较模糊的, 所以这篇打算从native层进行分析, 尝试搞清楚DexClassLoader与PathClassLoader区别的本质.
一、java层
1.1 PathClassLoader与DexClassLoader初始化
// 1. 注意PathClassLoader与DexClassLoader不同点仅在于初始化时传给BaseDexClassLoader时是否支持optimizedDirectory;
// 2. 正如DexClassLoader的类注释描述, DexClassLoader支持加载外部jar、apk中的dex中的类, 这个特殊点正是因为optimizedDirectory;
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
// 一个类加载器,用于加载包含{@code classes.dex}条目的{@code .jar}和{@code .apk}文件中的类。
// 这可用于执行未作为应用程序的一部分安装的代码。
// 此类加载器需要一个应用程序专用的可写目录来缓存优化的类。使用{@code Context.getCodeCacheDir()}来创建
// 这样的目录:{@ code File dexOutputDir = context.getCodeCacheDir();}
// 不要在外部存储上缓存优化的类, 外部存储不提供保护应用程序免受代码注入攻击所必需的访问控制。
public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
}
1.2 DexPathList初始化
public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) {
this.definingContext = definingContext;
// splitDexPath不分析, 就是常规切割, 继续分析makeDexElements;
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
1.3 DexPathList.makeDexElements
private static final String DEX_SUFFIX = ".dex";
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory) {
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();
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 if (file.isFile()){
// 这里一定要注意哈, 当前的file可能是.dex、 .jar、 .apk三种类型;
if (name.endsWith(DEX_SUFFIX)) {
// 当前文件是.dex文件
// Raw dex file (not inside a zip/jar).
dex = loadDexFile(file, optimizedDirectory);
} else {// 当前文件是.jar或者是.apk文件
zip = file;
dex = loadDexFile(file, optimizedDirectory);
}
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
1.4 DexPathList.loadDexFile
private static DexFile loadDexFile(File file, File optimizedDirectory) {
if (optimizedDirectory == null) {
// 如果optimizedDirectory为null, 则直接返回DexFile();
return new DexFile(file);
} else {
// optimizedPath = new File(optimizedDirectory, file.getName()).getPath();
// 注意optimizedPath是指优化后的dex文件存放的路径;
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}
private static String optimizedPathFor(File path, File optimizedDirectory) {
// 我们不想使用“.odex”,因为构建系统将其用于与仅资源jar文件配对的文件。如果VM可以假设匹配的jar中没有classes.dex,
// 则不需要打开jar来检查更新的依赖项,从而在启动时提供轻微的性能提升。这里使用“.dex”匹配对/ data / dalvik-cache
// 中文件的使用。
String fileName = path.getName();
File result = new File(optimizedDirectory, fileName);
return result.getPath();
}
1.5 DexFile.loadDex
// loadDex最终也是调用了DexFile();
static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) {
// 我们可能想要缓存先前打开的DexFile对象。缓存将与close()同步.
// 当应用程序决定多次打开它时,这将有助于我们避免多次映射相同的DEX.
return new DexFile(sourcePathName, outputPathName, flags);
}
1.6 DexFile初始化
// 触发native层的加载;
private DexFile(String sourceName, String outputName, int flags) throws IOException {
mCookie = openDexFile(sourceName, outputName, flags);
mFileName = sourceName;
guard.open("close");
}
private static long openDexFile(String sourceName, String outputName, int flags) throws IOException {
// Use absolute paths to enable the use of relative paths when testing on host.
return openDexFileNative(new File(sourceName).getAbsolutePath(),
(outputName == null) ? null : new File(outputName).getAbsolutePath(),
flags);
}
// sourceName: 对应原始jar/apk中的.dex;
// outputName: dex文件优化后存放的地方;
private static native long openDexFileNative(String sourceName, String outputName, int flags);
二、native层dex文件加载
2.1 dalvik_system_DexFile.DexFile_openDexFileNative
// javaSourceName: 源.jar/.apk中的.dex
// javaOutputName: 对应Java层的optimizedDirectory;
static jlong DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
ScopedUtfChars sourceName(env, javaSourceName);
NullableScopedUtfChars outputName(env, javaOutputName);
ClassLinker* linker = Runtime::Current()->GetClassLinker();
// 创建DexFile的集合, 用于存放获取的DexFile对象;
std::unique_ptr<std::vector<const DexFile*>> dex_files(new std::vector<const DexFile*>());
std::vector<std::string> error_msgs;
// 从oat中加载优化后的.dex文件;
bool success = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs, dex_files.get());
return static_cast<jlong>(reinterpret_cast<uintptr_t>(dex_files.release()));
}
2.2 class_linker.OpenDexFilesFromOat
// dex_location: 对应sourceName, 可能是.dex、 .jar、 .apk其中的一种;
// oat_location: 对应outputName, 对应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) {
// 在进行dex优化之前, 需要做三件事:
// (1) 内存缓存: 首先检查我们是否已经打开了对应的oat文件, 这里可以理解为内存缓存, 在内存中是否有这样的一份缓存;
// (2) 磁盘缓存: 如果没有打开过对应的oat文件, 再次判断是否已经做过dex优化, 并且在磁盘中缓存了对应的oat文件,
// (3) 如果磁盘中存在,
// 1) Check whether we have an open oat file.
// This requires a dex checksum, use the "primary" one.
uint32_t dex_location_checksum;
uint32_t* dex_location_checksum_pointer = &dex_location_checksum;
bool have_checksum = true;
std::string checksum_error_msg;
if (!DexFile::GetChecksum(dex_location, dex_location_checksum_pointer, &checksum_error_msg)) {
// This happens for pre-opted files since the corresponding dex files are no longer on disk.
dex_location_checksum_pointer = nullptr;
have_checksum = false;
}
bool needs_registering = false;
// (1) 首先校验目标oat文件是否存在, 也就是说与javaSourceName和javaOutputName对应的目标oat文件
// 是否已经被加载过, 如果被加载 过, 则直接从内存缓存中获取目OatFile对象.
const OatFile::OatDexFile* oat_dex_file = FindOpenedOatDexFile(oat_location, dex_location,
dex_location_checksum_pointer);
// 尝试通过OatDexFile获取目标OatFile并为OatFile* open_oat_file赋值, 接下来以open_oat_file为
// 线索进行分析.
std::unique_ptr<const OatFile> open_oat_file(
oat_dex_file != nullptr ? oat_dex_file->GetOatFile() : nullptr);
// 这里开始第二步, 如果没有打开过对应的oat文件, 又分两种情况, 一种是磁盘中有, 也就是进行过dex优化,
// 另一种是磁盘中没有, 也就是没有进行过dex优化.
if (open_oat_file.get() == nullptr) {
if (oat_location != nullptr) {// 这里是第一种情况, 进行过dex优化操作.
//...
} else {// 这里是第二种情况, 未进行过dex优化操作.
//...
}
// 标志位, 如果没有缓存过目标OatFile, 此时将标志位置为true, 后续创建OatFile对象之后
// 根据该标志位确认是否需要进行缓存.
needs_registering = true;
}
// (3) 尝试从磁盘中找到目标OatFile对象.
bool success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location,
dex_location_checksum_pointer,
false, error_msgs, dex_files);
if (success) {
const OatFile* oat_file = open_oat_file.release(); // Avoid deleting it.
if (needs_registering) {
// 将目标oat_file进行注册, 以便于下次使用
RegisterOatFile(oat_file);
}
return oat_file->IsExecutable();
} else {
...
}
// (4) 如果没有对应的oat文件或者不匹配, 则需要进行重新生成和加载.
std::string cache_location;
// oat_location对应java层的optimizedDirectory.
if (oat_location == nullptr) {
// Use the dalvik cache.
const std::string dalvik_cache(GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA)));
cache_location = GetDalvikCacheFilenameOrDie(dex_location, dalvik_cache.c_str());
// 如果oat_location = null, 我们使用android系统默认路径: /data/dalvik-cache为oat默认路径;
oat_location = cache_location.c_str();
}
bool has_flock = true;
if (Runtime::Current()->IsDex2OatEnabled() && has_flock && scoped_flock.HasFile()) {
// 创建oat文件
open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(),
oat_location, error_msgs));
}
// 再次尝试从OatFile中获取目标DexFile
success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location,
dex_location_checksum_pointer,
true, error_msgs, dex_files);
if (success) {
// 如果获取成功, 缓存该OatFile
RegisterOatFile(open_oat_file.release());
return true;
} else {
return false;
}
}
2.3 class_linker.FindOpenedOatDexFile
// 从oat_files_查找是否存在对应的OatFile, 查找过程知道, OatFile缓存了oat_location;
// oat_location: 对应java层的optimizedDirectory
const OatFile::OatDexFile* ClassLinker::FindOpenedOatDexFile(const char* oat_location,
const char* dex_location,
const uint32_t* dex_location_checksum) {
ReaderMutexLock mu(Thread::Current(), dex_lock_);
for (const OatFile* oat_file : oat_files_) {
DCHECK(oat_file != nullptr);
if (oat_location != nullptr) {
if (oat_file->GetLocation() != oat_location) {
continue;
}
}
// 如果oat_files_不存在与oat_location对应的OatFile, 是不会执行到这里的;
// 如果存在与oat_location对应的OatFile, 根据OatFile获取oat_dex_file;
const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location,
dex_location_checksum,
false);
if (oat_dex_file != nullptr) {
return oat_dex_file;
}
}
return nullptr;
}
如果存在已经打开的oat文件, 这里会遍历所有打开的oat文件, 查找目标oat_file, 如果存在, 则返回该OatDexFile对象.
这里还有很关键的一个点:
就是我们在加载Class时是从优化后的Dex中加载, 源码这里的顺序是先判断是否存在目标OatDexFile, 而OatDexFile与oat_location也就是java层的optimizedDirectory是一一对应的关系, 再结合模块<2.2L59~L65>对oat_location的赋值, 大致明白DexClassLoader与PathClassLoader的本质区别, DexClassLoader不支持传入optimizedDirectory, 所以被优化后的dex文件都存放在固定目录下, 如果使用DexClassLoader加载也只能加载固定目录下的dex文件, 而PathClassLoader支持optimizedDirectory, 被优化后的dex文件存放在optimizedDirectory路径下, 所以支持加载外部dex文件.
2.4 class_linker.LoadMultiDexFilesFromOatFile
static bool LoadMultiDexFilesFromOatFile(const OatFile* oat_file,
const char* dex_location,
const uint32_t* dex_location_checksum,
bool generated,
std::vector<std::string>* error_msgs,
std::vector<const DexFile*>* dex_files) {
size_t old_size = dex_files->size(); // To rollback on error.
bool success = true;
for (size_t i = 0; success; ++i) {
// 获取目标OatDexFile对象.
const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(next_name, nullptr, false);
if (oat_dex_file == nullptr) {
break; // Not found, done.
}
// Checksum test. Test must succeed when generated.
success = !generated;
if (next_location_checksum_pointer != nullptr) {
success = next_location_checksum == oat_dex_file->GetDexFileLocationChecksum();
}
if (success) {
// 获取目标DexFile对象
const DexFile* dex_file = oat_dex_file->OpenDexFile(&error_msg);
if (dex_file == nullptr) {
success = false;
error_msgs->push_back(error_msg);
} else {
// 如果目标DexFile存在, 则将该DexFile进行压栈操作, 在dex_files中缓存起来.
dex_files->push_back(dex_file);
}
}
}
if (dex_files->size() == old_size) {
success = false; // We did not even find classes.dex
}
return success;
}
如果获取到目标DexFile, 将标志位置为true, 同时将DexFile进行压栈操作, 下次直接从栈获取即可, 如果没有获取到目标DexFile, 标志位置为false.
2.5 class_linker.CreateOatFileForDexLocation
const OatFile* ClassLinker::CreateOatFileForDexLocation(const char* dex_location,
int fd, const char* oat_location,
std::vector<std::string>* error_msgs) {
// Generate the output oat file for the dex file
VLOG(class_linker) << "Generating oat file " << oat_location << " for " << dex_location;
std::string error_msg;
// 创建.dex对应的oat文件, 如何创建的先不分析<TODO看完老罗的文章之后再做分析>
if (!GenerateOatFile(dex_location, fd, oat_location, &error_msg)) {
CHECK(!error_msg.empty());
error_msgs->push_back(error_msg);
return nullptr;
}
// 创建完成之后再打开该文件并创建OatFile实例;
std::unique_ptr<OatFile> oat_file(OatFile::Open(oat_location, oat_location, nullptr,
!Runtime::Current()->IsCompiler(),
&error_msg));
if (oat_file.get() == nullptr) {
std::string compound_msg = StringPrintf("\nFailed to open generated oat file '%s': %s",
oat_location, error_msg.c_str());
error_msgs->push_back(compound_msg);
return nullptr;
}
return oat_file.release();
}
2.6 oat_file.OpenDexFile
const DexFile* OatFile::OatDexFile::OpenDexFile(std::string* error_msg) const {
return DexFile::Open(dex_file_pointer_, FileSize(), dex_file_location_,
dex_file_location_checksum_, error_msg);
}
2.7 dex_file.Open
bool DexFile::Open(const char* filename, const char* location, std::string* error_msg,
std::vector<const DexFile*>* dex_files) {
uint32_t magic;
ScopedFd fd(OpenAndReadMagic(filename, &magic, error_msg));
// 如果filename是.zip格式, 即java层对应的.jar或者.apk;
if (IsZipMagic(magic)) {
// 从压缩包中获取.dex文件, 结合下文对OpenZip的分析可知, 将zip中的.dex文件放入dex_files中;
return DexFile::OpenZip(fd.release(), location, error_msg, dex_files);
}
// 如果filename是.dex格式;
if (IsDexMagic(magic)) {
// 则直接加载dex文件;
std::unique_ptr<const DexFile> dex_file(DexFile::OpenFile(fd.release(), location, true, error_msg));
if (dex_file.get() != nullptr) {
dex_files->push_back(dex_file.release());
return true;
} else {
return false;
}
}
*error_msg = StringPrintf("Expected valid zip or dex file: '%s'", filename);
return false;
}
2.8 dex_file.OpenZip
bool DexFile::OpenZip(int fd, const std::string& location, std::string* error_msg,
std::vector<const DexFile*>* dex_files) {
std::unique_ptr<ZipArchive> zip_archive(ZipArchive::OpenFromFd(fd, location.c_str(), error_msg));
return DexFile::OpenFromZip(*zip_archive, location, error_msg, dex_files);
}
2.9 dex_file.OpenFromZip
bool DexFile::OpenFromZip(const ZipArchive& zip_archive, const std::string& location,
std::string* error_msg, std::vector<const DexFile*>* dex_files) {
ZipOpenErrorCode error_code;
std::unique_ptr<const DexFile> dex_file(Open(zip_archive, kClassesDex, location, error_msg, &error_code));
if (dex_file.get() == nullptr) {
return false;
} else {
// Had at least classes.dex.
dex_files->push_back(dex_file.release());
// Now try some more.
size_t i = 2;
// We could try to avoid std::string allocations by working on a char array directly. As we
// do not expect a lot of iterations, this seems too involved and brittle.
while (i < 100) {
std::string name = StringPrintf("classes%zu.dex", i);
std::string fake_location = location + kMultiDexSeparator + name;
std::unique_ptr<const DexFile> next_dex_file(Open(zip_archive, name.c_str(), fake_location,
error_msg, &error_code));
if (next_dex_file.get() == nullptr) {
if (error_code != ZipOpenErrorCode::kEntryNotFound) {
LOG(WARNING) << error_msg;
}
break;
} else {
// 遍历zip文件, 将.dex文件放入到dex_files集合中;
dex_files->push_back(next_dex_file.release());
}
i++;
}
return true;
}
}
如果filename
(对应java层的dexPath)
对应的文件是一个.zip/.jar文件, 此时会对该包里面所有的dex文件进行dex优化操作, 这个知识点在对Multidex进行分析时会涉及到(为什么5.0以后不需要使用Multidex)
.
2.10 class_linker.RegisterOatFile
const OatFile* ClassLinker::RegisterOatFile(const OatFile* oat_file) {
WriterMutexLock mu(Thread::Current(), dex_lock_);
if (kIsDebugBuild) {
for (size_t i = 0; i < oat_files_.size(); ++i) {
CHECK_NE(oat_file, oat_files_[i]) << oat_file->GetLocation();
}
}
VLOG(class_linker) << "Registering " << oat_file->GetLocation();
// 缓存OatFile对象;
oat_files_.push_back(oat_file);
return oat_file;
}
网友评论