1,DexClassLoader和PathClassLoader
在android中,类文件的加载主要是通过这两个类加载器来实现的。其构造函数分别如下所示
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
其中DexClassLoader比PathClassLoader多了一个参数,即optimizedDirectory,这个参数代表着dex文件的缓存目录,即经过优化后的odex文件(ART模式下是oat)存储目录。PathClassLoader一般加载系统类或者作为应用的默认类加载器。
2,BaseDexClassLoader
DexClassLoader和PathClassLoader都继承自BaseDexClassLoader,如1所示,DexClassLoader和PathClassLoader的构造函数都是直接调用BaseDexClassLoader,其构造函数如下
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
可以看到,在BaseDexClassLoader中主要是创建了一个DexPathList的对象。
3,DexPathList
其构造函数如下
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
简单分析一下,首先对dex的路径做一下判断,如果是空的,就跑出异常。再对缓存目录检测一下,如果接受到的缓存目录不空,就看一下这个目录实际上存不存在,可不可以读,如果不可以就抛异常。接下来有一行代码
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);
这行代码调用makeDexElements函数,它创建了一个dex路径的元素数组,看一下这个函数
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions) {
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 (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 if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
|| name.endsWith(ZIP_SUFFIX)) {
zip = file;
try {
dex = loadDexFile(file, optimizedDirectory);
} 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);
}
} else 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 {
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的可能是一个单纯的以.dex结尾的文件,或者是一个.apk文件,或者是一个.jar文件,或者是一个.zip文件,对这些文件分别提取去dex文件来做处理,这里只说一下对.dex文件的处理。
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);
}
}
可以看到它调用了loadDexFile函数来对dex文件作出处理,并返回一个DexFile的对象。
loadDexFile函数如下所示
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);
}
}
可以看到,loadDexFile函数就是单纯的创建了一个DexFile的实例对象。如果缓存目录是空,则直接以传入的dex文件为参数创建DexFile,否则调用DexFile的loadDex函数创建一个DexFile对象。DexFile的loadDex只是为DexFile的构造函数设置了一些路径参数,然后调用DexFile的构造函数创建一个DexFile对象。
4,DexFile
DexFile一共三个构造函数,分别如下所示
public DexFile(File file) throws IOException {
this(file.getPath());
}
public DexFile(String fileName) throws IOException {
mCookie = openDexFile(fileName, null, 0);
mFileName = fileName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie);
}
private DexFile(String sourceName, String outputName, int flags) throws IOException {
if (outputName != null) {
try {
String parent = new File(outputName).getParent();
if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
throw new IllegalArgumentException("Optimized data directory " + parent
+ " is not owned by the current user. Shared storage cannot protect"
+ " your application from code injection attacks.");
}
} catch (ErrnoException ignored) {
// assume we'll fail with a more contextual error later
}
}
mCookie = openDexFile(sourceName, outputName, flags);
mFileName = sourceName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie);
}
第一个构造函数调用了第二个构造函数,所以总共两类构造函数。一个是接受单个dex路径的,另外一个接受dex路径和缓存目录的,这也对应上面提到的PathClassLoader和DexClassLoader。简单分析一下这两类构造函数,可以看到它们都调用了openDexFile函数。分别是:
mCookie = openDexFile(fileName, null, 0);
mCookie = openDexFile(sourceName, outputName, flags);
openDexFile函数会调用一个native函数openDexFileNative来执行真正的dex文件加载,并会返回一个虚拟机cookie值,通过这个cookie就可以找到被加载的dex。
5,DalvikdalviksystemDexFileopenDexFileNative
第四步中提到会调用native的openDexFileNative来执行真正的dex文件加载,这个函数就对应着DalvikdalviksystemDexFileopenDexFileNative。其主要源码如下
static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,JValue* pResult)
{
StringObject* sourceNameObj = (StringObject*) args[0];
StringObject* outputNameObj = (StringObject*) args[1];
DexOrJar* pDexOrJar = NULL;
JarFile* pJarFile;
RawDexFile* pRawDexFile;
char* sourceName;
char* outputName;
if (sourceNameObj == NULL) {
dvmThrowNullPointerException("sourceName == null");
RETURN_VOID();
}
sourceName = dvmCreateCstrFromString(sourceNameObj);
……
/*
* Try to open it directly as a DEX if the name ends with ".dex".
* If that fails (or isn't tried in the first place), try it as a
* Zip with a "classes.dex" inside.
*/
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");
}
if (pDexOrJar != NULL) {
pDexOrJar->fileName = sourceName;
addToDexFileTable(pDexOrJar);
} else {
free(sourceName);
}
free(outputName);
RETURN_PTR(pDexOrJar);
}
上面的代码根据sourceName的不同(是.dex文件还是.jar文件等)来做处理,原理是一样的。下面来分析一下是dex文件的情形,首先是调用dvmRawDexFileOpen函数来直接打开dex文件,打开后会将相应数据结构存放到RawDexFile中。然后动态创建一下DexOrJar对象,并将一些关于dex的信息,尤其是RawDexFile的对象存放进去。最后再通过addToDexFileTable函数将这个DexOrJar对象存放到哈希表中。接下来首先分析一下dvmRawDexFileOpen函数的实现,再分析一下addToDexFileTable的实现。
6,dvmRawDexFileOpen
其源码如下所示:
int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName,
RawDexFile** ppRawDexFile, bool isBootstrap)
{
DvmDex* pDvmDex = NULL;
char* cachedName = NULL;
int result = -1;
int dexFd = -1;
int optFd = -1;
u4 modTime = 0;
u4 adler32 = 0;
size_t fileSize = 0;
bool newFile = false;
bool locked = false;
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);
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;
}
/*
* See if the cached file matches. If so, optFd will become a reference
* to the cached file and will have been seeked to just past the "opt"
* header.
*/
if (odexOutputName == NULL) {
cachedName = dexOptGenerateCacheFileName(fileName, NULL);
if (cachedName == NULL)
goto bail;
} else {
cachedName = strdup(odexOutputName);
}
ALOGV("dvmRawDexFileOpen: Checking cache for %s (%s)",
fileName, cachedName);
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;
/*
* If optFd points to a new file (because there was no cached
* version, or the cached version was stale), generate the
* optimized DEX. The file descriptor returned is still locked,
* and is positioned just past the optimization header.
*/
if (newFile) {
u8 startWhen, copyWhen, endWhen;
bool result;
off_t dexOffset;
dexOffset = lseek(optFd, 0, SEEK_CUR);
result = (dexOffset > 0);
if (result) {
startWhen = dvmGetRelativeTimeUsec();
result = copyFileToFile(optFd, dexFd, fileSize) == 0;
copyWhen = dvmGetRelativeTimeUsec();
}
if (result) {
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);
}
/*
* Map the cached version. This immediately rewinds the fd, so it
* doesn't have to be seeked anywhere in particular.
*/
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);
*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;
}
简单分析一下这段代码:
6.1 首先打开dex文件并获取对应的文件描述符。
6.2 然后调用 verifyMagicAndGetAdler32函数验证dex文件的magic头,并且获取保存在dex文件头部的checksum,验证方法很简单,就是进行简单的头部字符串比较,读取checksum注意是按照默认的小端模式进行相应的移位 。
6.3 接着调用getModTimeAndSize获取dex文件的时间戳(上次修改时间)和文件大小(对应的文件字节数)
6.4 判断传入的odexOutputName参数(即缓存目录)是否为空,如果为空,就调用dexOptGenerateCacheFileName将其缓存文件命名为/data/dalvik-cache/@..@格式的文件(把dex路径中的/换位@)。
6.5 调用dvmOpenCachedDexFile函数创建一个缓存文件(Dalvik下是odex文件),并返回一个文件描述符。如果缓存文件不存在或者过时,就会创建一个新的。然后在这个新文件中填充一些空的opt头部信息(调用dexOptCreateEmptyHeader函数),填充信息时会填充一个真实的dexOffset信息,其它field暂时无效,都填充为1。如果缓存文件已经存在,就会对这个文件做一些校验,主要是验证一个头部信息和依赖信息。
6.6 如果6,5步创建了一个新的缓存文件,就接着往这个文件里填充信息。首先调用copyFileToFile往odex文件中填充整个dex文件数据。然后调用dvmOptimizeDexFile,而这个函数又会新创建一个进程并调用dexopt程序来进行dex优化工作,后面再详细分析dexopt的优化流程。
6.7 调用dvmDexFileOpenFromFd函数来将odex文件映射到内存中,并解析其内容。首先调用sysMapFileInShmemWritableReadOnly来将odex映射到内存。然后调用dexFileParse来对odex文件做一下校验,如checksum等信息,并将内存中的odex信息填充到一个DexFile对象中,主要是一个field信息的指针,具体如下。
pDexFile->baseAddr = data;
pDexFile->pHeader = pHeader;
pDexFile->pStringIds = (const DexStringId*) (data + pHeader->stringIdsOff);
pDexFile->pTypeIds = (const DexTypeId*) (data + pHeader->typeIdsOff);
pDexFile->pFieldIds = (const DexFieldId*) (data + pHeader->fieldIdsOff);
pDexFile->pMethodIds = (const DexMethodId*) (data + pHeader->methodIdsOff);
pDexFile->pProtoIds = (const DexProtoId*) (data + pHeader->protoIdsOff);
pDexFile->pClassDefs = (const DexClassDef*) (data + pHeader->classDefsOff);
pDexFile->pLinkData = (const DexLink*) (data + pHeader->linkOff);
然后调用allocateAuxStructures来创建一个辅助结构。首先是申请一段内存空间,在这个空间中存放着一个DvmDex对象和一些索引信息。DvmDex中存放的是在这段内存空间中相应的field索引区地址,这段存放field索引信息的空间中则指向着所有的filed信息(此时还没有链接上,只是分配了空间)。这里的field有string,class,method,field,interface。
6.8 创建一个RawDexFile对象,并将缓存文件名,DvmDex等数据存放进去
7,将6中得到的RawDexFile等信息存放到DexOrJar指向的一个对象中,然后再将这个DexOrJar通过addToDexFileTable存放到hash表中。
至此,类加载器的创建就完成了。