Dalvik虚拟机学习2——类加载器的创建
来源:互联网 发布:思迅软件费用 编辑:程序博客网 时间:2024/06/05 15:42
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表中。
至此,类加载器的创建就完成了。
- Dalvik虚拟机学习2——类加载器的创建
- Dalvik虚拟机【3】——类加载
- Dalvik虚拟机加载类的机制
- 【Android高级】Dalvik虚拟机及其类加载器讲解
- Dalvik虚拟机学习1——Dalvik虚拟机源码结构分析
- Android虚拟机学习总结Dalvik虚拟机进程和线程的创建过程分析
- Dalvik虚拟机【2】——Dex文件格式
- Dalvik中类的加载
- Dalvik虚拟机中so的加载和JNI调用
- Dalvik虚拟机中so的加载和JNI调用
- android平台的基础——Dalvik 虚拟机
- Android的java虚拟机——从Dalvik到ART
- Dalvik虚拟机进程和线程的创建过程分析
- Dalvik虚拟机进程和线程的创建过程分析
- Dalvik虚拟机进程和线程的创建过程分析
- Dalvik虚拟机进程和线程的创建过程分析
- Dalvik虚拟机进程和线程的创建过程分析
- Dalvik虚拟机进程和线程的创建过程分析
- JOptionPane类提示框的一些常用的方法
- MySQL学习笔记_04
- java开发中常用到小方法
- 3.3 Linux获取系统信息(5、6、7节)
- 前端面试经典问题——css居中的几种方式
- Dalvik虚拟机学习2——类加载器的创建
- 日常(停课后的月考)
- 【地图整饰】ArcMap添加图框及ArcEngine实现
- windows server 2008 下安装MySQL 不同版本数据库
- 数据结构实验之排序二:交换排序
- org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actua
- css中清除浮动展开父元素的几种方法总结
- WebGL笔记_着色器(三)
- sql优化