从源码分析 Android dexClassLoader 加载机制原理
来源:互联网 发布:淘宝怎么入驻企业店铺 编辑:程序博客网 时间:2024/05/22 05:26
DexClassLoader 是加载包含classes.dex文件的jar文件或者apk文件; 通过构造函数发现需要一个应用私有的,可写的目录去缓存优化的classes。可以用使用File dexoutputDir = context.getDir(“dex”,0);创建一个这样的目录,不要使用外部缓存,以保护你的应用被代码注入。
其源码如下:
public classDexClassLoaderextendsBaseDexClassLoader {
37 /**38 * Creates a {@code DexClassLoader} that finds interpreted and native39 * code. Interpreted classes are found in a set of DEX files contained40 * in Jar or APK files.41 *42 * <p>The path lists are separated using the character specified by the43 * {@code path.separator} system property, which defaults to {@code :}.44 *45 * @param dexPath the list of jar/apk files containing classes and46 * resources, delimited by {@code File.pathSeparator}, which47 * defaults to {@code ":"} on Android48 * @param optimizedDirectory directory where optimized dex files49 * should be written; must not be {@code null}50 * @param libraryPath the list of directories containing native51 * libraries, delimited by {@code File.pathSeparator}; may be52 * {@code null}53 * @param parent the parent class loader54 */55 public DexClassLoader(String dexPath, String optimizedDirectory,56 String libraryPath, ClassLoader parent) {57 super(dexPath, new File(optimizedDirectory), libraryPath, parent);58 }59}60
再解释下几个构造函数参数的意义:
dexpath为jar或apk文件目录。
optimizedDirectory为优化dex缓存目录。
libraryPath包含native lib的目录路径。
parent父类加载器。
然后执行的是父类的构造函数:
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
BaseDexClassLoader 的构造函数如下:
public BaseDexClassLoader(String dexPath, File optimizedDirectory,String libraryPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);}
第一句调用的还是父类的构造函数,也就是ClassLoader的构造函数:
protected ClassLoader(ClassLoader parentLoader) { this(parentLoader, false); } /* * constructor for the BootClassLoader which needs parent to be null. */ ClassLoader(ClassLoader parentLoader, boolean nullAllowed) { if (parentLoader == null && !nullAllowed) { throw new NullPointerException(“parentLoader == null && !nullAllowed”); } parent = parentLoader;}
该构造函数把传进来的父类加载器赋给了私有变量parent。
再来看第二句:
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);pathList为该类的私有成员变量,类型为DexPathList,进入到DexPathList函数:
/**78 * Constructs an instance.79 *80 * @param definingContext the context in which any as-yet unresolved81 * classes should be defined82 * @param dexPath list of dex/resource path elements, separated by83 * {@code File.pathSeparator}84 * @param libraryPath list of native library directory path elements,85 * separated by {@code File.pathSeparator}86 * @param optimizedDirectory directory where optimized {@code .dex} files87 * should be found and written to, or {@code null} to use the default88 * system directory for same89 */90 public DexPathList(ClassLoader definingContext, String dexPath,91 String libraryPath, File optimizedDirectory) {9293 if (definingContext == null) {94 throw new NullPointerException("definingContext == null");95 }9697 if (dexPath == null) {98 throw new NullPointerException("dexPath == null");99 }100101 if (optimizedDirectory != null) {102 if (!optimizedDirectory.exists()) {103 throw new IllegalArgumentException(104 "optimizedDirectory doesn't exist: "105 + optimizedDirectory);106 }107108 if (!(optimizedDirectory.canRead()109 && optimizedDirectory.canWrite())) {110 throw new IllegalArgumentException(111 "optimizedDirectory not readable/writable: "112 + optimizedDirectory);113 }114 }115116 this.definingContext = definingContext;117118 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
119 // save dexPath for BaseDexClassLoader120 this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,1 suppressedExceptions);122123 // Native libraries may exist in both the system and124 // application library paths, and we use this search order:125 //126 // 1. This class loader's library path for application libraries (libraryPath):127 // 1.1. Native library directories128 // 1.2. Path to libraries in apk-files129 // 2. The VM's library path from the system property for system libraries130 // also known as java.library.path131 //132 // This order was reversed prior to Gingerbread; see http://b/2933456.133 this.nativeLibraryDirectories = splitPaths(libraryPath, false);134 this.systemNativeLibraryDirectories =135 splitPaths(System.getProperty("java.library.path"), true);136 List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);137 allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);138139 this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null,140 suppressedExceptions);141142 if (suppressedExceptions.size() > 0) {143 this.dexElementsSuppressedExceptions =144 suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);145 } else {146 dexElementsSuppressedExceptions = null;147 }148 }149
前面是一些对于传入参数的验证,然后调用了makeDexElements。
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) {ArrayList<Element> elements = new ArrayList<Element>(); for (File file : files) { File zip = null; DexFile dex = null; String name = file.getName(); if (name.endsWith(DEX_SUFFIX)) { //dex文件处理 // 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 if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX) || name.endsWith(ZIP_SUFFIX)) { //apk,jar,zip文件处理 zip = file; try { dex = loadDexFile(file, optimizedDirectory); } catch (IOException suppressed) { suppressedExceptions.add(suppressed); } } else if (file.isDirectory()) { elements.add(new Element(file, true, null, null)); } else { System.logW(“Unknown file type for: ” + file); } if ((zip != null) || (dex != null)) { elements.add(new Element(file, false, zip, dex)); } } return elements.toArray(new Element[elements.size()]); }}
不管是dex文件,还是apk文件最终加载的都是loadDexFile,跟进这个函数:
如果optimizedDirectory为null就会调用openDexFile(fileName, null, 0);加载文件。否则调用DexFile.loadDex(file.getPath(), optimizedPath, 0);而这个函数也只是直接调用new DexFile(sourcePathName, outputPathName, flags);里面调用的也是openDexFile(sourceName, outputName, flags);所以最后都是调用openDexFile,跟进这个函数:
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); }}
private static int openDexFile(String sourceName, String outputName, int flags) throws IOException { return openDexFileNative(new File(sourceName).getCanonicalPath(), (outputName == null) ? null : new File(outputName).getCanonicalPath(), flags);}
而这个函数调用的是so的openDexFileNative这个函数。打开成功则返回一个cookie。
接下来就是分析native函数的实现部分了。
———-openDexFileNative———-
static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,JValue* pResult){ ……………if (hasDexExtension(sourceName) && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) { ALOGV(“Opening DEX file ‘%s’ (DEX)”, sourceName); pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar)); pDexOrJar->isDex = true; pDexOrJar->pRawDexFile = pRawDexFile; pDexOrJar->pDexMemory = NULL; } else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) { ALOGV(“Opening DEX file ‘%s’ (Jar)”, sourceName); pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar)); pDexOrJar->isDex = false; pDexOrJar->pJarFile = pJarFile; pDexOrJar->pDexMemory = NULL; } else { ALOGV(“Unable to open DEX file ‘%s’”, sourceName); dvmThrowIOException(“unable to open DEX file”); } ……………}
这里会根据是否为dex文件或者包含classes.dex文件的jar,分别调用函数dvmRawDexFileOpen和dvmJarFileOpen来处理,最终返回一个DexOrJar的结构。
首先来看dvmRawDexFileOpen函数的处理:
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
RawDexFile** ppRawDexFile, bool isBootstrap)
{
.................
dexFd = open(fileName, O_RDONLY);
if (dexFd < 0) goto bail;
/* If we fork/exec into dexopt, don't let it inherit the open fd. */
dvmSetCloseOnExec(dexFd);
//校验前8个字节的magic是否正确,然后把校验和保存到adler32
if (verifyMagicAndGetAdler32(dexFd, &adler32) < 0) {
ALOGE("Error with header for %s", fileName);
goto bail;
}
//得到文件修改时间以及文件大小
if (getModTimeAndSize(dexFd, &modTime, &fileSize) < 0) {
ALOGE("Error with stat for %s", fileName);
goto bail;
}
.................
//调用函数dexOptCreateEmptyHeader,构造了一个DexOptHeader结构体,写入fd并返回
optFd = dvmOpenCachedDexFile(fileName, cachedName, modTime,
adler32, isBootstrap, &newFile, /*createIfMissing=*/true);
if (optFd < 0) {
ALOGI("Unable to open or create cache for %s (%s)",
fileName, cachedName);
goto bail;
}
locked = true;
//如果成功生了opt头
if (newFile) {
u8 startWhen, copyWhen, endWhen;
bool result;
off_t dexOffset;
dexOffset = lseek(optFd, 0, SEEK_CUR);
result = (dexOffset > 0);
if (result) {
startWhen = dvmGetRelativeTimeUsec();
// 将dex文件中的内容写入文件的当前位置,也就是从dexOffset的偏移处开始写
result = copyFileToFile(optFd, dexFd, fileSize) == 0;
copyWhen = dvmGetRelativeTimeUsec();
}
if (result) {
//对dex文件进行优化
result = dvmOptimizeDexFile(optFd, dexOffset, fileSize,
fileName, modTime, adler32, isBootstrap);
}
if (!result) {
ALOGE("Unable to extract+optimize DEX from '%s'", fileName);
goto bail;
}
endWhen = dvmGetRelativeTimeUsec();
ALOGD("DEX prep '%s': copy in %dms, rewrite %dms",
fileName,
(int) (copyWhen - startWhen) / 1000,
(int) (endWhen - copyWhen) / 1000);
}
//dvmDexFileOpenFromFd这个函数最主要在这里干了两件事情
// 1.将优化后得dex文件(也就是odex文件)通过mmap映射到内存中,并通过mprotect修改它的映射内存为只读权限
// 2.将映射为只读的这块dex数据中的内容全部提取到DexFile这个数据结构中去
if (dvmDexFileOpenFromFd(optFd, &pDvmDex) != 0) {
ALOGI("Unable to map cached %s", fileName);
goto bail;
}
if (locked) {
/* unlock the fd */
if (!dvmUnlockCachedDexFile(optFd)) {
/* uh oh -- this process needs to exit or we'll wedge the system */
ALOGE("Unable to unlock DEX file");
goto bail;
}
locked = false;
}
ALOGV("Successfully opened '%s'", fileName);
//填充结构体 RawDexFile
*ppRawDexFile = (RawDexFile*) calloc(1, sizeof(RawDexFile));
(*ppRawDexFile)->cacheFileName = cachedName;
(*ppRawDexFile)->pDvmDex = pDvmDex;
cachedName = NULL; // don't free it below
result = 0;
bail:
free(cachedName);
if (dexFd >= 0) {
close(dexFd);
}
if (optFd >= 0) {
if (locked)
(void) dvmUnlockCachedDexFile(optFd);
close(optFd);
}
return result;
}
最后成功的话,填充RawDexFile。
dvmJarFileOpen的代码处理也是差不多的。
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
JarFile** ppJarFile, bool isBootstrap)
{
...
...
...
//调用函数dexZipOpenArchive来打开zip文件,并缓存到系统内存里
if (dexZipOpenArchive(fileName, &archive) != 0)
goto bail;
archiveOpen = true;
...
//这行代码设置当执行完成后,关闭这个文件句柄
dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));
...
//优先处理已经优化了的Dex文件
fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
...
//从压缩包里找到Dex文件,然后打开这个文件
entry = dexZipFindEntry(&archive, kDexInJarName);
...
//把未经过优化的Dex文件进行优化处理,并输出到指定的文件
if (odexOutputName == NULL) {
cachedName = dexOptGenerateCacheFileName(fileName,
kDexInJarName);
}
...
//创建缓存的优化文件
fd = dvmOpenCachedDexFile(fileName, cachedName,
dexGetZipEntryModTime(&archive, entry),
dexGetZipEntryCrc32(&archive, entry),
isBootstrap, &newFile, /*createIfMissing=*/true);
...
//调用函数dexZipExtractEntryToFile从压缩包里解压文件出来
if (result) {
startWhen = dvmGetRelativeTimeUsec();
result = dexZipExtractEntryToFile(&archive, entry, fd) == 0;
extractWhen = dvmGetRelativeTimeUsec();
}
...
//调用函数dvmOptimizeDexFile对Dex文件进行优化处理
if (result) {
result = dvmOptimizeDexFile(fd, dexOffset,
dexGetZipEntryUncompLen(&archive, entry),
fileName,
dexGetZipEntryModTime(&archive, entry),
dexGetZipEntryCrc32(&archive, entry),
isBootstrap);
}
...
//调用函数dvmDexFileOpenFromFd来缓存dex文件
//并分析文件的内容。比如标记是否优化的文件,通过签名检查Dex文件是否合法
if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {
ALOGI("Unable to map %s in %s", kDexInJarName, fileName);
goto bail;
}
...
//保存文件到缓存里,标记这个文件句柄已经保存到缓存
if (locked) {
/* unlock the fd */
if (!dvmUnlockCachedDexFile(fd)) {
/* uh oh -- this process needs to exit or we'll wedge the system */
ALOGE("Unable to unlock DEX file");
goto bail;
}
locked = false;
}
...
//设置一些相关信息返回前面的函数处理。
*ppJarFile = (JarFile*) calloc(1, sizeof(JarFile));
(*ppJarFile)->archive = archive;
(*ppJarFile)->cacheFileName = cachedName;
(*ppJarFile)->pDvmDex = pDvmDex;
cachedName = NULL; // don't free it below
result = 0;
...
}
最后成功的话,填充JarFile。
- 从源码分析 Android dexClassLoader 加载机制原理
- DexClassLoader动态加载分析
- android DexClassLoader 加载pak
- Android源码分析-资源加载机制
- Android源码分析-资源加载机制
- Android源码分析-资源加载机制
- Android源码分析-资源加载机制
- Android源码分析-资源加载机制
- Android源码分析-资源加载机制
- Android源码分析-资源加载机制
- Android源码分析-资源加载机制解析
- Android源码分析-资源加载机制
- Android源码分析-资源加载机制
- [Android] 从源码分析 Handler 消息机制
- 从android源码角度分析touch机制
- Android 从源码分析Handler消息机制
- Android应用开发原理之从ViewGroup源码分析ViewGroup的事件分发机制
- Android4.4.2 DexClassLoader源码分析
- 摄像头采集,264编码,live555直播(1)
- 多线程之NSThread
- 编译属于自己的openwrt固件
- Android Fragment生命周期和参数传递
- cloudera安装报错 socket.gaierror: [Errno -2] Name or service not known
- 从源码分析 Android dexClassLoader 加载机制原理
- Android:开发规范
- 209,自定义代理例子与数据加载结合
- #ifdef __cplusplus extern "C" { #endif 的解释
- #!/bin/bash
- C# string字符串转换为字节数组和转换为16进制的字符串形式
- Android 代码动态加载布局
- #define中的#,\,##,#@符号,以及#define的基本应用
- 档案重要吗有什么作用(转载记录避免以后麻烦)