Android Dex文件加载流程

Android 系统加载Dex 分两步:
首先是开发者通过Jave Api 调用加载Dex的方法;
然后是jvm调用Native的方法进行加载Dex文件

JAVA中加载一个DEX

DexClassLoader classLoader = new DexClassLoader(dexPath, OutputDir.getAbsolutePath(),null,getClassLoader())

可以看到在创建DexClassLoader的时候,是需要传入dex文件的路径,查看DexClassLoader源代码。

public BaseDexClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
ClassLoader[] sharedLibraryLoadersAfter,
boolean isTrusted) {
super(parent);
// Setup shared libraries before creating the path list. ART relies on the class loader
// hierarchy being finalized before loading dex files.
this.sharedLibraryLoaders = sharedLibraryLoaders == null
? null
: Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);
// 关键代码
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

this.sharedLibraryLoadersAfter = sharedLibraryLoadersAfter == null
? null
: Arrays.copyOf(sharedLibraryLoadersAfter, sharedLibraryLoadersAfter.length);
// Run background verification after having set 'pathList'.
this.pathList.maybeRunBackgroundVerification(this);

reportClassLoaderChain();
}

最终是通过DexPathList来加载dex

DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
....

ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// save dexPath for BaseDexClassLoader
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext, isTrusted);

// Native libraries may exist in both the system and
// application library paths, and we use this search order:
//
// 1. This class loader's library path for application libraries (librarySearchPath):
// 1.1. Native library directories
// 1.2. Path to libraries in apk-files
// 2. The VM's library path from the system property for system libraries
// also known as java.library.path
//
// This order was reversed prior to Gingerbread; see http://b/2933456.
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
...
}

makeDexElements

private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
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();

DexFile dex = null;
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
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 {
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);
}
}
if (dex != null && isTrusted) {
dex.setTrusted();
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}

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);
}
}

最终看到,DEX文件的加载时发生在DexFile

DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements)
throws IOException {
mCookie = openDexFile(fileName, null, 0, loader, elements);
mInternalCookie = mCookie;
mFileName = fileName;
//System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
}

openDexFile
最终跟踪下去,最后是调用了native中openDexFileNative来加载DEX

private static native Object openDexFileNative(String sourceName, String outputName, int flags,
ClassLoader loader, DexPathList.Element[] elements);

Native中加载DEX

在上面的步骤中,已经知道最终是通过openDexFileNative来加载DEX的,在源码网站上搜索该方法,很容易就搜索出实现的地方,不出所料,果然是在dalvik模块中
![[Pasted image 20220924170359.png]]

static jobject DexFile_openDexFileNative(JNIEnv* env,
jclass,
jstring javaSourceName,
jstring javaOutputName ATTRIBUTE_UNUSED,
jint flags ATTRIBUTE_UNUSED,
jobject class_loader,
jobjectArray dex_elements) {
ScopedUtfChars sourceName(env, javaSourceName);
if (sourceName.c_str() == nullptr) {
return nullptr;
}

std::vector<std::string> error_msgs;
const OatFile* oat_file = nullptr;
std::vector<std::unique_ptr<const DexFile>> dex_files =
Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
class_loader,
dex_elements,
/*out*/ &oat_file,
/*out*/ &error_msgs);
return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);
}

由于在ART虚拟机中,是可以通过将DEX转为OAT来提高程序运行速度,但是这个不是强制的,通过一个技术手段可以中断这个过程。

查看oat_file_manager#OpenDexFilesFromOat

std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(
const char* dex_location,
jobject class_loader,
jobjectArray dex_elements,
const OatFile** out_oat_file,
std::vector<std::string>* error_msgs) {
...
// 在这可以看到核心方法
added_image_space = runtime->GetClassLinker()->AddImageSpace(image_space.get(), h_loader,
/*out*/&dex_files,
/*out*/&temp_error_msg);
...

return dex_files;
}

看到是虚拟机的runtime的GetClassLinker的AddImageSpace将文件加载到内存中,关于AddImageSpace解析

class_linker.cc

bool ClassLinker::AddImageSpace(
gc::space::ImageSpace* space,
Handle<mirror::ClassLoader> class_loader,
std::vector<std::unique_ptr<const DexFile>>* out_dex_files,
std::string* error_msg) {
// 一大堆的虚拟机操作
...
for (auto dex_cache : dex_caches.Iterate<mirror::DexCache>()) {
std::string dex_file_location = dex_cache->GetLocation()->ToModifiedUtf8();
// 看到核心的加载dex的方法了
std::unique_ptr<const DexFile> dex_file = OpenOatDexFile(oat_file,
dex_file_location.c_str(),
error_msg);
if (dex_file == nullptr) {
return false;
}

{
// Native fields are all null. Initialize them.
WriterMutexLock mu(self, *Locks::dex_lock_);
dex_cache->Initialize(dex_file.get(), class_loader.Get());
}
if (!app_image) {
// Register dex files, keep track of existing ones that are conflicts.
AppendToBootClassPath(dex_file.get(), dex_cache);
}
out_dex_files->push_back(std::move(dex_file));
}
...
}

一大堆的虚拟机操作后,然后看到是OpenOatDexFile将dex文件加载到内存中,一顿
OpenOatDexFile

static std::unique_ptr<const DexFile> OpenOatDexFile(const OatFile* oat_file,
const char* location,
std::string* error_msg)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(error_msg != nullptr);
std::unique_ptr<const DexFile> dex_file;
const OatDexFile* oat_dex_file = oat_file->GetOatDexFile(location, nullptr, error_msg);
if (oat_dex_file == nullptr) {
return std::unique_ptr<const DexFile>();
}
std::string inner_error_msg;
// oat 中获取DEX
dex_file = oat_dex_file->OpenDexFile(&inner_error_msg);
if (dex_file == nullptr) {
*error_msg = StringPrintf("Failed to open dex file %s from within oat file %s error '%s'",
location,
oat_file->GetLocation().c_str(),
inner_error_msg.c_str());
return std::unique_ptr<const DexFile>();
}

if (dex_file->GetLocationChecksum() != oat_dex_file->GetDexFileLocationChecksum()) {
*error_msg = StringPrintf("Checksums do not match for %s: %x vs %x",
location,
dex_file->GetLocationChecksum(),
oat_dex_file->GetDexFileLocationChecksum());
return std::unique_ptr<const DexFile>();
}
return dex_file;
}

看到是从oat_dex_file->OpenDexFile(&inner_error_msg)获取的dex
art/runtime/oat_file.cc#OpenDexFile

std::unique_ptr<const DexFile> OatDexFile::OpenDexFile(std::string* error_msg) const {
ScopedTrace trace(__PRETTY_FUNCTION__);
static constexpr bool kVerify = false;
static constexpr bool kVerifyChecksum = false;
const ArtDexFileLoader dex_file_loader;
return dex_file_loader.Open(dex_file_pointer_,
FileSize(),
dex_file_location_,
dex_file_location_checksum_,
this,
kVerify,
kVerifyChecksum,
error_msg);
}

在这已经看到了dex文件的指针了

art/libdexfile/dex/art_dex_file_loader.cc#Open

std::unique_ptr<const DexFile> ArtDexFileLoader::OpenCommom(
const uint8_t* base,
size_t size,
const std::string& location,
uint32_t location_checksum,
const OatDexFile* oat_dex_file,
bool verify,
bool verify_checksum,
std::string* error_msg,
std::unique_ptr<DexFileContainer> container) const {
ScopedTrace trace(std::string("Open dex file from RAM ") + location);
return OpenCommon(base,
size,
/*data_base=*/ nullptr,
/*data_size=*/ 0u,
location,
location_checksum,
oat_dex_file,
verify,
verify_checksum,
error_msg,
std::move(container),
/*verify_result=*/ nullptr);
}

最后是在OpenCommon中进行dex文件的加载,在这个方法中,base就是dex文件的指针,size是dex文件的大小。

art/libdexfile/dex/art_dex_file_loader.cc#OpenCommon