美文网首页
Dalvik虚拟机对dex的加载过程

Dalvik虚拟机对dex的加载过程

作者: DroidMind | 来源:发表于2017-09-12 19:03 被阅读0次

    涉及源码(Android 4.4.2):
    /libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
    /libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
    /libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
    /libcore/dalvik/src/main/java/dalvik/system/DexFile.java
    /dalvik/vm/native/dalvik_system_DexFile.cpp
    /dalvik/vm/RawDexFile.cpp
    /dalvik/vm/JarFile.cpp

    Java层

    通过我们会通过创建一个DexClassLoader来加载我们的dex,下面就以此为切入点进行

    dexClassLoader = new DexClassLoader(apkPath, getFilesDir().getAbsolutePath(), null, getClassLoader());
    

    查看DexClassLoader的构造方法。

    /libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

    public class DexClassLoader extends BaseDexClassLoader {
        // dexPath:是加载apk/dex/jar的路径
        // optimizedDirectory:是优化dex后得到的.odex文件的输出路径
        // libraryPath:是加载的时候需要用到的so库
        // parent:给DexClassLoader指定父加载器
        public DexClassLoader(String dexPath, String optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(dexPath, new File(optimizedDirectory), libraryPath, parent);
        }
    }
    

    可以看到它调用的是父类的构造函数,所以直接来看BaseDexClassLoader的构造函数。

    /libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
    

    创建了一个DexPathList实例,下面来看看DexPathList的构造函数。

    /libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

    private final Element[] dexElements;
    
    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions);
    }
    

    它调用的是makeDexElements方法来创建一个Element数组来存放Element对象,每个Element对象包含一个DexFile对象。

    static class Element {
    
        private final DexFile dexFile;
    
    }
    

    下面先看看makeDexElements方法。

    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();
    
            // 如果是一个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);
                }
            // 如果是一个apk或者jar或者zip文件
            } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                    || name.endsWith(ZIP_SUFFIX)) {
                zip = file;
    
                try {
                    // 1、调用loadDexFile加载dex文件,得到一个DexFile对象
                    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);
            }
            
            // 2、把DexFile对象封装到Element对象中,然后将Element对象加入Element数组
            if ((zip != null) || (dex != null)) {
                elements.add(new Element(file, false, zip, dex));
            }
        }
    
        return elements.toArray(new Element[elements.size()]);
    }
    

    重点看看loadDexFile加载dex文件

    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);
        }
    }
    

    在DexFile.loadDex方法,其实调用的也是创建一个DexFile对象,所以我们需要关注的是DexFile对象的创建方法。

    static public DexFile loadDex(String sourcePathName, String outputPathName,
        int flags) throws IOException {
        return new DexFile(sourcePathName, outputPathName, flags);
    }
    
    

    /libcore/dalvik/src/main/java/dalvik/system/DexFile.java

    public DexFile(String fileName) throws IOException {
        mCookie = openDexFile(fileName, null, 0);
        mFileName = fileName;
        guard.open("close");
    }
    

    可以看到,它调用openDexFile来进行dex的加载。

    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);
    }
    
    native private static int openDexFileNative(String sourceName, String outputName,
        int flags) throws IOException;
    

    最终调用的是一个openDexFileNative的native方法来进行加载,加载完成之后,会返回一个int值,它对应的是加载dex的指针。

    整个过程如下图所示:

    dexload2.png

    C/C++层

    下面进入从native层进行分析,openDexFileNative方法对应的native层方法就是dalvik_system_DexFile.cpp文件的Dalvik_dalvik_system_DexFile_openDexFileNative方法。

    /dalvik/vm/native/dalvik_system_DexFile.cpp

    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;
    
        sourceName = dvmCreateCstrFromString(sourceNameObj);
        if (outputNameObj != NULL)
            outputName = dvmCreateCstrFromString(outputNameObj);
        else
            outputName = NULL;
         
        // 1、尝试把它当做一个后缀为.dex的DEX文件进行打开,得到RawDexFile结构数据
        // 2、如果打开失败,则把它当做一个包含有classes.dex文件的Zip文件进行打开,得到JarFile结构数据
        if (hasDexExtension(sourceName)
                && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
            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;
        }
    
        if (pDexOrJar != NULL) {
            pDexOrJar->fileName = sourceName;
            // 3、把pDexOrJar这个结构体中的内容加到gDvm中的userDexFile结构的hash表中,以便dalvik以后的查找
            addToDexFileTable(pDexOrJar);
        } else {
            free(sourceName);
        }
    
        free(outputName);
        RETURN_PTR(pDexOrJar);
    }
    

    我们传入的可以是dex文件也可以是一个apk文件,所以分两种情况处理,但是处理方式基本一致。

    1、后缀为.dex的DEX文件处理,得到RawDexFile结构数据

    /dalvik/vm/RawDexFile.cpp

    /* See documentation comment in header. */
    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;
        
        // 1、打开.dex文件
        dexFd = open(fileName, O_RDONLY);
        if (dexFd < 0) goto bail;
        
        // 2、验证dex版本信息,并且获取adler32值存放到adler32里面
        if (verifyMagicAndGetAdler32(dexFd, &adler32) < 0) {
            ALOGE("Error with header for %s", fileName);
            goto bail;
        }
        
        // 3、得到dex文件的修改时间和文件大小,分别保存在变量modTime和filesize中 
        if (getModTimeAndSize(dexFd, &modTime, &fileSize) < 0) {
            ALOGE("Error with stat for %s", fileName);
            goto bail;
        }
    
       // 4、如果优化dex后的输出目录为空,则会生成一个目录,否则odexOutputName为输出目录
        if (odexOutputName == NULL) {
            cachedName = dexOptGenerateCacheFileName(fileName, NULL);
            if (cachedName == NULL)
                goto bail;
        } else {
            cachedName = strdup(odexOutputName);
        }
        // 5、调用函数dexOptCreateEmptyHeader,构造了一个DexOptHeader结构体,写入fd并返回
        optFd = dvmOpenCachedDexFile(fileName, cachedName, modTime,
            adler32, isBootstrap, &newFile, /*createIfMissing=*/true);
    
        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.
         */
        // 如果成功生成了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();
                // 6、将dex文件中的内容拷贝到当前odex文件,从dexOffset开始
                result = copyFileToFile(optFd, dexFd, fileSize) == 0;
                copyWhen = dvmGetRelativeTimeUsec();
            }
    
            if (result) {
                //7、对dex文件进行优化,并将优化数据写入odex文件
                result = dvmOptimizeDexFile(optFd, dexOffset, fileSize,
                    fileName, modTime, adler32, isBootstrap);
            }
    
    
            endWhen = dvmGetRelativeTimeUsec();
        }
    
        // 8、将odex文件数据转换为pDvmDex结构
        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);
        //9、分配内存,填充结构体 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;
    }
    
    

    这种方式的处理思路如下图所示:

    dexload.png
    2、包含有classes.dex文件的Zip文件打开方式,得到JarFile结构数据

    /dalvik/vm/JarFile.cpp

    int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
        JarFile** ppJarFile, bool isBootstrap)
    {
        ZipArchive archive;
        DvmDex* pDvmDex = NULL;
        char* cachedName = NULL;
        bool archiveOpen = false;
        bool locked = false;
        int fd = -1;
        int result = -1;
    
        // 1、打开zip文件,存放在archive中
        if (dexZipOpenArchive(fileName, &archive) != 0)
            goto bail;
        archiveOpen = true;
    
        dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));
    
        fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
        if (fd >= 0) {
            ALOGV("Using alternate file (odex) for %s ...", fileName);
            if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
                ALOGE("%s odex has stale dependencies", fileName);
                free(cachedName);
                cachedName = NULL;
                close(fd);
                fd = -1;
                goto tryArchive;
            } else {
                ALOGV("%s odex has good dependencies", fileName);
            }
        } else {
            ZipEntry entry;
    
    tryArchive:
           //2、从压缩包里找到Dex文件,然后打开这个文件
            entry = dexZipFindEntry(&archive, kDexInJarName);
            if (entry != NULL) {
                bool newFile = false;
    
               // 如果优化dex后的输出目录为空,则会生成一个目录,否则odexOutputName为输出目录
                if (odexOutputName == NULL) {
                    cachedName = dexOptGenerateCacheFileName(fileName,
                                    kDexInJarName);
                    if (cachedName == NULL)
                        goto bail;
                } else {
                    cachedName = strdup(odexOutputName);
                }
               // 3、调用函数dexOptCreateEmptyHeader,构造了一个DexOptHeader结构体,写入fd并返回
                fd = dvmOpenCachedDexFile(fileName, cachedName,
                        dexGetZipEntryModTime(&archive, entry),
                        dexGetZipEntryCrc32(&archive, entry),
                        isBootstrap, &newFile, /*createIfMissing=*/true);
                
                locked = true;
    
                // 如果成功生成了opt头
                if (newFile) {
                    u8 startWhen, extractWhen, endWhen;
                    bool result;
                    off_t dexOffset;
    
                    dexOffset = lseek(fd, 0, SEEK_CUR);
                    result = (dexOffset > 0);
    
                    if (result) {
                        startWhen = dvmGetRelativeTimeUsec();
                        // 4、将dex文件中的内容拷贝到当前odex文件
                        result = dexZipExtractEntryToFile(&archive, entry, fd) == 0;
                        extractWhen = dvmGetRelativeTimeUsec();
                    }
                    if (result) {
                        //5、对dex文件进行优化,并将优化数据写入odex文件
                        result = dvmOptimizeDexFile(fd, dexOffset,
                                    dexGetZipEntryUncompLen(&archive, entry),
                                    fileName,
                                    dexGetZipEntryModTime(&archive, entry),
                                    dexGetZipEntryCrc32(&archive, entry),
                                    isBootstrap);
                    }
    
                    endWhen = dvmGetRelativeTimeUsec();
                   
                }
            } else {
                goto bail;
            }
        }
    
        // 6、将odex文件数据转换为pDvmDex结构
        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;
        }
    
        ALOGV("Successfully opened '%s' in '%s'", kDexInJarName, fileName);
        // 7、分配内存,填充结构体 RawDexFile
        *ppJarFile = (JarFile*) calloc(1, sizeof(JarFile));
        (*ppJarFile)->archive = archive;
        (*ppJarFile)->cacheFileName = cachedName;
        (*ppJarFile)->pDvmDex = pDvmDex;
        cachedName = NULL;      // don't free it below
        result = 0;
    
    bail:
        if (archiveOpen && result != 0)
            dexZipCloseArchive(&archive);
        free(cachedName);
        if (fd >= 0) {
            if (locked)
                (void) dvmUnlockCachedDexFile(fd);
            close(fd);
        }
        return result;
    }
    
    

    这种方式的处理思路如下图所示:

    dexload1.png
    从java层到native层整个过程图如下:
    dexload3.png

    参考文章:
    http://bbs.pediy.com/thread-199230.htm
    http://bbs.pediy.com/thread-197274.htm

    相关文章

      网友评论

          本文标题:Dalvik虚拟机对dex的加载过程

          本文链接:https://www.haomeiwen.com/subject/oeqhsxtx.html