dex文件加载流程(一)

dex文件加载系列文章链接:

  1. PathClassLoader、DexClassLoader

    dex文件加载流程(一)

    dex文件加载流程(二)

    dex文件加载流程(三)

  2. InMemoryDexClassLoader

    InMemoryDexClassLoader加载内存dex的流程

一、PathClassLoader和DexClassLoader

Android启动APP时,会加载dex文件,而与加载dex文件相关的是两个ClassLoader:

  1. PathClassLoader

    只能加载系统中已经安装过的apk

  2. DexClassLoader

    可以加载jar、apk、dex,也可以从SD卡中加载未安装的apk

PathClassLoader和DexClassLoader对应代码分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//路径:/libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {

public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}

public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}

}

//路径:/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {

public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}

}

两者的父类都是BaseDexClassLoader,且都是用父类的四个参数的构造方法,对应代码如下:

1
2
3
4
5
6
7
8
9
10
//路径:/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);

if (reporter != null) {
reporter.report(this.pathList.getDexPaths());
}
}

在构造方法中,创建一个DexPathList实例。

二、DexPathList()

DexPathList代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//路径:/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
public DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory) {
......
this.definingContext = definingContext;

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

this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);//

if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}

构造方法中,主要是调用了makeDexElements()方法(makePathElements()先不讲),赋值给this.dexElements,该类型为Element数组,查看Element类的代码可知,类中有DexFile类型的dexFile字段。

三、makeDexElements()

回到makeDexElements()方法,该方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//路径:/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader) {
Element[] elements = new Element[files.size()];
int elementsPos = 0;
//遍历所有文件
for (File file : files) {
if (file.isDirectory()) {//文件夹
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) {//文件
String name = file.getName();

if (name.endsWith(DEX_SUFFIX)) {// 判断文件后缀名,DEX_SUFFIX = ".dex"
try {
//调用loadDexFile加载dex文件
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 {//zip file?
DexFile dex = null;
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {//zip中没有dex文件
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;
}

遍历所有文件,寻找dex文件并调用loadDexFile()加载dex文件。

四、loadDexFile()

该方法如下:

1
2
3
4
5
6
7
8
9
10
//路径:/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
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文件优化。在Android系统中,应用程序首次启动,会对应用的dex文件进行优化,并在cache/dalvik-cache目录下生成缓存文件,以加快应用的启动速度。代码中optimizedDirectory即指缓存文件所在路径,默认cache/dalvik-cache目录。

回到代码中,如果APP没有相应的缓存文件,则通过调用DexFile()来解析dex文件,否则调用DexFile.loadDex()方法解析缓存文件。

4.1 DexFile()

对应代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
//路径:/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)
throws IOException {
this(file.getPath(), loader, elements);
}

//进而调用
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()方法,现在这里停下。

4.2 DexFile.loadDex()

对应代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//路径:/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
static DexFile loadDex(String sourcePathName, String outputPathName,
int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
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);
mInternalCookie = mCookie;
mFileName = sourceName;
//System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
}

可以看到,不管调用的是new DexFile()还是DexFile.loadDex() ,最终都会调用openDexFile()

五、openDexFile()

对应代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
//路径:/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
private static Object openDexFile(String sourceName, String outputName, int flags,
ClassLoader loader, DexPathList.Element[] elements) 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,
loader,
elements);
}

最终调用openDexFileNative()方法,顾名思义,这是个native方法,在目录art/runtime/native/dalvik_system_DexFile.cc下。

在下一篇文章中,将继续沿着openDexFileNative()方法分析dex文件加载流程。

六、总览图


参考:

入门ART虚拟机(1)——加载DEX文件 - 简书 (jianshu.com)

Dex文件加载以及类加载流程_两个dex间能引用类吗-CSDN博客

http://aospxref.com/android-8.0.0_r36/


dex文件加载流程(一)
http://example.com/2023/11/20/Android安全/dex文件加载流程(一)/
作者
gla2xy
发布于
2023年11月20日
许可协议