声明:原创文章,转载请备注来源: http://shuwoom.com/?p=269&preview=true 

5.1 dexodex文件结构和关系

    上图是Dex文件和Odex文件的结构和关系图

    应用程序在第一次启动app的时候会在/dalvik/dalvik-cache目录下生成odex文件结构,其实就是在原appdex文件结构基础上,增加odex文件头和并在原dex文件末尾增加依赖库信息和辅助信息

    依赖库信息知名该dex文件所需要的本地函数库通过下图我们可以看到该本地函数库其实就是/system/framework下的jar包。

    辅助信息记录了dex中类的索引表,dalvik为每一个dex文件创建一个对应DexClassLookup对象用于记dex文件所有的类索引信息,DexClassLookup对象为的每个类都生成了table结构体对象,该对象记录了类的描述符的哈希值、类描述符在dex文件中的偏移和类在dex文件中的偏移。其table结构体对象定义如下。也就是说通过该table结构体对象就可以对dex文件中某一个类快速定位。

5.2 DexDexFileRawDexFileDvmDexClassObject之间的关系

    在开始分析学习Dalvik加载解析dex过程的源码之前我们先来了解几个重要的数据结构之间的关系,Dalvik加载解析dex的过程就是紧紧围绕这些数据结构进行其本质就是把加载的dex解析之后最终以ClassObject的数据结构的形式保存在内存,方便在读取执行具体类方法指令的时候,可以快速查找执行。最终传递给解释器执行的是字节码指令,保存这个指令信息的正是Method结构体对象中的insns成员,改结构体对象是解释器的重要输入。而该结构体对象正是ClassObject结构体中的成员之一。ClassObject结构体对象几乎包含类目标在运行时所需的所有资源,包括当前类的超类、当前类使用的类加载器、Method类型指针等。

    在第二章的2.3.4节,我们知道app在启动过程中创建了PathClassLoader加载dex文件。Dalvik加载和解析dex文件的操作就发生在PathClassLoader类中我们从该类的构造函数开始分析

位置:Android源码/android/app/ApplicationLoaders.java

class ApplicationLoaders{

public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent){

        //父类加载器为BootClassLoader

        ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();

 

        synchronized (mLoaders) {

            if (parent == null) {

                parent = baseParent;

            }

           

            if (parent == baseParent) {

                ClassLoader loader = mLoaders.get(zip);

                if (loader != null) {

                    return loader;

                }

               …

                PathClassLoader pathClassloader = new PathClassLoader(zip, libPath, parent);

                …

                mLoaders.put(zip, pathClassloader);

                return pathClassloader;

            }

            …

            PathClassLoader pathClassloader = new PathClassLoader(zip, parent);

            …

            return pathClassloader;

        }

    }

}

getClassLoader方法这里创建了PathClassLoader类对象,我们跳转到该类的构造函数。

位置libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java

public class PathClassLoader extends BaseDexClassLoader {

    

    public PathClassLoader(String dexPath, ClassLoader parent) {

        super(dexPath, null, null, parent);

    }

 

    public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) {

        super(dexPath, null, libraryPath, parent);

    }

}

PathClassLoader类继承BaseDexClassLoader父类PathClassLoader构造函数最终调用的是父类的构造函数。下面我们跳转到BaseDexClassLoader类的构造函数。

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

public class BaseDexClassLoader extends ClassLoader {

private final DexPathList pathLis

 

public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {

        super(parent);

        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);

    }

}

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

final class DexPathList {

    private static final String DEX_SUFFIX = “.dex”;

    private static final String JAR_SUFFIX = “.jar”;

    private static final String ZIP_SUFFIX = “.zip”;

private static final String APK_SUFFIX = “.apk”;

 

private final Element[] dexElements;

    

    public DexPathList(ClassLoader definingContext,String dexPath,String libraryPath, File optimizedDirectory) {

        …

        this.definingContext = definingContext;

        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();

        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,

                                           suppressedExceptions);

        …

        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);

}

static class Element {

        private final File file;

        private final boolean isDirectory;

        private final File zip;

        private final DexFile dexFile;

        private ZipFile zipFile;

        private boolean initialized;

    …

}

}

DexPathList有一个很关键的成员:Element数组dexElements变量。我们看下Element类的结构,可以知道,Element主要是用来保存dex或资源文件路径的类。我们知道一些app方法超过65535个的时候,就需要使用多dex的方案进行处理,如下图所示,所以这里是Element数组而不是一个Element

spliteDexPath方法是传递进来的dex路径字符串分割转换成File对象数组然后调用makeDexElements方法加载dex、jar、apkzip结尾的文件

下面我们看下makeDexElements方法是如何创建Element数组对象的。

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

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)) {

                // dex结尾的文件

                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()]);

    }

makeDexElements遍历所有文件我们看到加载dex文件的关键函数loadDexFile它最终返回一个DexFile结构对象我们接着分析loadDexFile函数

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

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加载dex有两种情况,一种是没有设置保存优化dex目录的时候,则直接创建一个DexFile对象,否则就直接调用DexFile类的loadDex方法完成家在解析dex工作。

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

public final class DexFile {

    private int mCookie;

private final String mFileName;

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 {

        …

        mCookie = openDexFile(sourceName, outputName, flags);

        mFileName = sourceName;

        guard.open(“close”);

        //System.out.println(“DEX FILE cookie is ” + mCookie);

    }

 

 

    static public DexFile loadDex(String sourcePathName, String outputPathName,

        int flags) throws IOException {

        return new DexFile(sourcePathName, outputPathName, flags);

    }

    

    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;

    …

 

}

我们可以看到,上面两种情况函数调用流程如下:

1new DexFile(File file)è DexFile(String fileName)è openDexFileè openDexFileNative

2loadDexè DexFile(String sourceName, String outputName, int flags)è openDexFileè openDexFileNative

可以看到最终两种情况都调用了一个native方法openDexFilenative,只是传递的参数不同而已。

openDexFileNative方法最终返回一个cookie标识这个标识跟加载到内存中的dex数据结构相关联下面我们进入native层分析openDexFileNative

位置:Android源码/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;

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

}

可以看到Dalvik_dalvik_system_DexFile_openDexFileNative方法的核心部分是dvmRawDexFileOpendvmJarFileOpen当传递进来的是dex文件类型的时候,调用dvmRawDexFileOpen加载解析并将解析后的文件信息保存到RawDexFile结构体中如果不是dex文件类型例如dex文件包含在压缩包中则调用dvmJarFileOpen加载解析并将解析后的文件信息保存到JarFile。接着,将两种情况解析后的信息都保存到DexOrJar结构体对象中,最后调用addToDexFileTable方法将当前解析后的DexOrJar对象保存到全局变量gDvm.userDexFiles哈希表数组中

这里我们主要分析dvmRawDexFileOpen的实现,dvmJarFileOpen方法过车类似区别就是多了一个提取压缩包中dex文件的过程

位置:Android源码/dalvik/vm/RawDexFile.cpp

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

//获取优化后的dex文件名称

if (odexOutputName == NULL) {

        cachedName = dexOptGenerateCacheFileName(fileName, NULL);

        if (cachedName == NULL)

            goto bail;

    } else {

        cachedName = strdup(odexOutputName);

    }

optFd = dvmOpenCachedDexFile(fileName, cachedName, modTime,

        adler32, isBootstrap, &newFile, /*createIfMissing=*/true);

//优化dex文件

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

        }

}

//加载解析dex文件

if (dvmDexFileOpenFromFd(optFd, &pDvmDex) != 0) {

        ALOGI(“Unable to map cached %s”, fileName);

        goto bail;

    }

}

5.3 Dex文件优化过程

位置:Android源码/dalvik/vm/analysis/DexPrepare.cpp

bool dvmOptimizeDexFile(int fd, off_t dexOffset, long dexLength,

    const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)

{

    …

pid_t pid;

pid = fork();

if (pid == 0) {

    

    static const char* kDexOptBin = “/bin/dexopt”;

    …

    androidRoot = getenv(“ANDROID_ROOT”);

        if (androidRoot == NULL) {

            ALOGW(“ANDROID_ROOT not set, defaulting to /system”);

            androidRoot = “/system”;

        }

        execFile = (char*)alloca(strlen(androidRoot) + strlen(kDexOptBin) + 1);

        strcpy(execFile, androidRoot);

        strcat(execFile, kDexOptBin); // /system/bin/dexopt

    …

    argv[curArg++] = execFile;

    argv[curArg++] = “–dex”;

    …

    const char* argv[argc+1];

    …

    if (kUseValgrind)

            execv(kValgrinder, const_cast<char**>(argv));

        else

            execv(execFile, const_cast<char**>(argv));

}

}

这里我们看到dvmOptimizeDexFile最终是创建了一个子进程,并调用/system/bin/dexopt程序进行优化工作

dexopt的主程序代码位于Android源码/dalvik/dexopt/OptMain.cpp文件中。其入口函数为main方法。

int main(int argc, char* const argv[])

{

    set_process_name(“dexopt”);

 

    setvbuf(stdout, NULL, _IONBF, 0);

 

    if (argc > 1) {

        if (strcmp(argv[1], “–zip”) == 0)

            return fromZip(argc, argv);

        else if (strcmp(argv[1], “–dex”) == 0)

            return fromDex(argc, argv);

        else if (strcmp(argv[1], “–preopt”) == 0)

            return preopt(argc, argv);

    }

 

    …

}

由于上面传递过来的是”–dex”参数这里我们是调用fromDex方法进行优化

位置:dexopt的主程序代码位于Android源码/dalvik/dexopt/OptMain.cpp

static int fromDex(int argc, char* const argv[])

{

//启动一个虚拟机进程

    if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode, flags) != 0) {

        …

    }

//进行优化

if (!dvmContinueOptimization(fd, offset, length, debugFileName,

            modWhen, crc, (flags & DEXOPT_IS_BOOTSTRAP) != 0))

    {

        …

    }

}

fromDex方法里调用了2个关键函数,dvmPrepForDexOpt用于启动一个虚拟机进程,然后调用了dvmContinueOptimization方法进行实际的优化工作

位置:Android源码/dalvik/vm/analysis/DexPrepare.cpp

bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,

    const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)

{

void* mapAddr;

    mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

    …

    success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength, doVerify, doOpt, &pClassLookup, NULL);

    if (success) {

        DvmDex* pDvmDex = NULL;

        u1* dexAddr = ((u1*) mapAddr) + dexOffset;

        

        if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) {

             …

        }else{

            …

            DexHeader* pHeader = (DexHeader*)pDvmDex->pHeader;

            updateChecksum(dexAddr, dexLength, pHeader);

            …

        }

    }

}

5.4 Dex文件加载解析过程

回到dvmRawDexFileOpen方法在调用dvmOptimizeDexFile方法完成dex文件的优化后接着调用dvmDexFileOpenFromFd方法将上一步优化后的dex文件映射到内存中并进行加载和解析

位置:Android源码/dalvik/vm/DvmDex.cpp

int dvmDexFileOpenFromFd(int fd, DvmDex** ppDvmDex)

{

 

}

位置:Android源码/dalvik/libdex/DexFile.cpp

DexFile* dexFileParse(const u1* data, size_t length, int flags)

{

 

}

位置:Android源码/dalvik/libdex/DexFile.cpp

DexFile* dexFileParse(const u1* data, size_t length, int flags)

{

 

}

 

总结

    Dalvik在加载dex文件并执行某个类的方法其整个流程可以用下图表示。类加载机制最终的目标就是为目标类生成一个ClassObject的数据结构的实例对象,并将加载了的这个类的ClassObject对象添加到全局变量gDvmloadedClasses成员中,该成员主要是保存加载到内存中的类对象。

Dalvik要运行某一个类方法的时候,是通过运行在内存中的ClassObject对象中的资源去执行,最后调用dvmInterpret方法初始化解释器并执行字节码指令。具体的细节会在下一章中详细讲解。

参考

[1] Dalvik加载dex过程分析 http://zke1ev3n.me/2016/04/12/Dalvik%E5%8A%A0%E8%BD%BDdex%E8%BF%87%E7%A8%8B%E5%88%86%E6%9E%90/

[2] Dalvik and ART http://202.197.96.98/cache/10/03/newandroidbook.com/75c0878af2978850a35ea842e2acf90e/Andevcon-DEX.pdf

[3] Androiddex文件的加载与优化流程 http://blog.csdn.net/jsqfengbao/article/details/52103439

发表评论

电子邮件地址不会被公开。 必填项已用*标注