Android 类加载器 DexClassLoader和PathClassLoader

Android中的两个重要的类加载器DexClassLoaderPathClassLoader,专门用于加载Android平台的类。使用ClassLoader.getSystemClassLoader()获取到的类加载器,也和Java应用程序中的加载器不同,在Android上,获取到的是PathClassLoader

在系统源码中的路径:

  • libcore/dalvik/
  • dalvik/libdex

PathClassLoader

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
package dalvik.system;
/**
* 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使用该类来作为系统类加载器和应用类加载器
*/
public class PathClassLoader extends BaseDexClassLoader {
/**
* Creates a {@code PathClassLoader} that operates on a given list of files
* and directories. This method is equivalent to calling
* {@link #PathClassLoader(String, String, ClassLoader)} with a
* {@code null} value for the second argument (see description there).
*
* @param 同下一个构造函数
* @param parent the parent class loader
*/
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
/**
* Creates a {@code PathClassLoader} that operates on two given
* lists of files and directories. The entries of the first list
* should be one of the following:
*
* <ul>
* <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as
* well as arbitrary resources.
* <li>Raw ".dex" files (not inside a zip file).
* </ul>
*
* The entries of the second list should be directories containing
* native library files.
*
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}

DexClassLoader

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
package dalvik.system;
import java.io.File;
/**
* 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.
*
* <p>This class loader requires an application-private, writable directory to
* cache optimized classes. Use {@code Context.getCodeCacheDir()} to create
* such a directory.
*
* <p><strong>Do not cache optimized classes on external storage.</strong>
* External storage does not provide access controls necessary to protect your
* application from code injection attacks.
*/
public class DexClassLoader extends BaseDexClassLoader {
/**
* Creates a {@code DexClassLoader} that finds interpreted and native
* code. Interpreted classes are found in a set of DEX files contained
* in Jar or APK files.
*
* <p>The path lists are separated using the character specified by the
* {@code path.separator} system property, which defaults to {@code :}.
*
* @param parent the parent class loader
*/
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}

PathClassLoader 和 DexClassLoader都继承自BaseDexClassLoader;

BaseDexClassLoader

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
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
/**
* @param dexPath 包含classes文件或者resources文件的jar/apk文件,
* 多个路径通过File.pathSeparator(Android中为":")分割(delimited)。
* @param optimizedDirectory dex文件解压后的存放目录,may be null
* @param libraryPath so库文件目录,多个目录以File.pathSeparator连接
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
// 构造DexPathList对象
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
throw cnfe;
}
return c;
}
@Override
protected URL findResource(String name) {
return pathList.findResource(name);
}
@Override
protected Enumeration<URL> findResources(String name) {
return pathList.findResources(name);
}
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
// ...
}

该类有一个DexPathList类型的pathList对象,可以看到该类的方法主要都是调用pathList对象来实现的。

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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/**
* A pair of lists of entries, associated with a {@code ClassLoader}.
* One of the lists is a dex/resource path &mdash; typically referred
* to as a "class path" &mdash; list, and the other names directories
* containing native code libraries. Class path entries may be any of:
* a {@code .jar} or {@code .zip} file containing an optional
* top-level {@code classes.dex} file as well as arbitrary resources,
* or a plain {@code .dex} file.
*
* <p>This class also contains methods to use these lists to look up
* classes and resources.</p>
*/
/*package*/ final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
private static final String zipSeparator = "!/";
/** class definition context */
private final ClassLoader definingContext;
/**
* dex文件或resource文件路径 (class path) elements.
* Facebook app uses reflection to modify 'dexElements' (http://b/7726934).
*/
private final Element[] dexElements;
/** List of native library path elements. */
private final Element[] nativeLibraryPathElements;
/** 应用程序的libraryDirectory */
private final List<File> nativeLibraryDirectories;
/**系统library文件目录 */
private final List<File> systemNativeLibraryDirectories;
/**
* Exceptions thrown during creation of the dexElements list.
*/
private final IOException[] dexElementsSuppressedExceptions;
/**
* Constructs an instance.
*
* @param definingContext the context in which any as-yet unresolved
* classes should be defined
* @param dexPath list of dex/resource path elements, separated by
* {@code File.pathSeparator}
* @param libraryPath list of native library directory path elements,
* separated by {@code File.pathSeparator}
* @param optimizedDirectory directory where optimized {@code .dex} files
* should be found and written to, or {@code null} to use the default
* system directory for same
*/
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// save dexPath for BaseDexClassLoader
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
// Native库可能存在在系统路径中,也可能存在于应用的application目录中,
// 查找library的时候,按照以下的顺序:
// 1. Native library directories
// 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(libraryPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
// 使用makePathElements构建libraryPath
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null,
suppressedExceptions);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}
/**
* Makes an array of dex/resource path elements, one per element of
* the given array.
*/
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions) {
List<Element> elements = new ArrayList<>();
/*
* Open all files and load the (direct or contained) dex files
* up front.
*/
for (File file : files) {
File zip = null;
File dir = new File("");
DexFile dex = null;
String path = file.getPath();
String name = file.getName();
if (path.contains(zipSeparator)) {
String split[] = path.split(zipSeparator, 2);
zip = new File(split[0]);
dir = new File(split[1]);
} else if (file.isDirectory()) {
// We support directories for looking up resources and native libraries.
// 如果file是一个目录,则可以用来查找资源文件和native library
elements.add(new Element(file, true, null, null));
} else if (file.isFile()) {
// 如果file是.dex,直接加载dexFile
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
} else { // 文件是zip,则加载成dex文件
zip = file;
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException suppressed) {
}
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(dir, false, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
/**
* Constructs a {@code DexFile} instance, as appropriate depending
* on whether {@code optimizedDirectory} is {@code null}.
*/
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}
// 加载类
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
// DexFile中去查找类
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
/**
*
* @return a URL to the named resource or {@code null} if the
* resource is not found in any of the zip/jar files
*/
public URL findResource(String name) {
for (Element element : dexElements) {
URL url = element.findResource(name);
if (url != null) {
return url;
}
}
return null;
}
/**
* @return the complete path to the library or {@code null} if no
* library was found
*/
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);
for (Element element : nativeLibraryPathElements) {
// 调用element#findNativeLibrary
String path = element.findNativeLibrary(fileName);
if (path != null) {
return path;
}
}
return null;
}

Element

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
static class Element {
private final File dir;
private final boolean isDirectory;
private final File zip;
private final DexFile dexFile;
private ZipFile zipFile;
private boolean initialized;
public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
this.dir = dir;
this.isDirectory = isDirectory;
this.zip = zip;
this.dexFile = dexFile;
}
// 初始化
public synchronized void maybeInit() {
if (initialized) {
return;
}
initialized = true;
if (isDirectory || zip == null) {
return;
}
try {
zipFile = new ZipFile(zip);
} catch (IOException ioe) {
System.logE("Unable to open zip file: " + zip, ioe);
zipFile = null;
}
}
public String findNativeLibrary(String name) {
maybeInit();
if (isDirectory) {
String path = new File(dir, name).getPath();
if (IoUtils.canOpenReadOnly(path)) {
return path;
}
} else if (zipFile != null) {
String entryName = new File(dir, name).getPath();
if (isZipEntryExistsAndStored(zipFile, entryName)) {
return zip.getPath() + zipSeparator + entryName;
}
}
return null;
}
// 查找到资源文件
public URL findResource(String name) {
maybeInit();
// We support directories so we can run tests and/or legacy code
// that uses Class.getResource.
if (isDirectory) {
// 如果是目录,则在里面查找文件,看是否存在。
File resourceFile = new File(dir, name);
if (resourceFile.exists()) {
try {
return resourceFile.toURI().toURL();
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
}
if (zipFile == null || zipFile.getEntry(name) == null) {
return null;
}
// 如果资源文件在zip中,则使用RFC1738规范,返回一个URL;
try {
/*
* File.toURL() is compliant with RFC 1738 in
* always creating absolute path names. If we
* construct the URL by concatenating strings, we
* might end up with illegal URLs for relative
* names.
*/
return new URL("jar:" + zip.toURL() + "!/" + name);
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
}

DexFile

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
public final class DexFile {
/**
* Loads a class. Returns the class on success, or a {@code null} reference
* on failure.
* <p>
* If you are not calling this from a class loader, this is most likely not
* going to do what you want. Use {@link Class#forName(String)} instead.
* <p>
* The method does not throw {@link ClassNotFoundException} if the class
* isn't found because it isn't reasonable to throw exceptions wildly every
* time a class is not found in the first DEX file we look at.
*/
public Class loadClass(String name, ClassLoader loader) {
String slashName = name.replace('.', '/');
return loadClassBinaryName(slashName, loader, null);
}
/**
* This takes a "binary" class name to better match ClassLoader semantics.
*
* @hide
*/
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie);
// 调用native方法加载Class
} catch (NoClassDefFoundError e) {
} catch (ClassNotFoundException e) {
}
return result;
}
}

打印输入Activity的getClassLoader()方法返回的classLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
dalvik.system.PathClassLoader[DexPathList[[
zip file "/data/app/com.ccf.dede-1/base.apk",
zip file "/data/app/com.ccf.dede-1/split_lib_dependencies_apk.apk",
zip file "/data/app/com.ccf.dede-1/split_lib_slice_0_apk.apk",
zip file "/data/app/com.ccf.dede-1/split_lib_slice_1_apk.apk",
zip file "/data/app/com.ccf.dede-1/split_lib_slice_2_apk.apk",
zip file "/data/app/com.ccf.dede-1/split_lib_slice_3_apk.apk",
zip file "/data/app/com.ccf.dede-1/split_lib_slice_4_apk.apk",
zip file "/data/app/com.ccf.dede-1/split_lib_slice_5_apk.apk",
zip file "/data/app/com.ccf.dede-1/split_lib_slice_6_apk.apk",
zip file "/data/app/com.ccf.dede-1/split_lib_slice_7_apk.apk",
zip file "/data/app/com.ccf.dede-1/split_lib_slice_8_apk.apk",
zip file "/data/app/com.ccf.dede-1/split_lib_slice_9_apk.apk"],
nativeLibraryDirectories=[
/data/app/com.ccf.dede-1/lib/arm64,
/vendor/lib64,
/system/lib64]]
]