Dalvik虚拟机对dex的加载过程

2017-09-12  本文已影响0人  DroidMind

涉及源码(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

上一篇下一篇

猜你喜欢

热点阅读