zoukankan      html  css  js  c++  java
  • DexClassLoader动态加载分析

    转载自:http://www.blogfshare.com/dexclassloader.html

    看到原来有把原始的dex文件加密保存,然后解密后使用DexClassLoader加载文件的方法,就来分析下DexClassLoader的加载流程:

    源码地址:http://androidxref.com/4.4_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

    该class加载器是加载包含classes.dex文件的jar文件或者apk文件,需要一个应用私有的,可写的目录去缓存优化的classes。可以用使用File dexoutputDir = context.getDir(“dex”,0);创建一个这样的目录,不要使用外部缓存,以保护你的应用被代码注入。

    构造方法如下:

    public class DexClassLoader extends BaseDexClassLoader {
        下面这段注释详细地说明了这个构造函数中各个参数地意义,不作阐述了,希望大家能够认真阅读,思考;

    public DexClassLoader(String dexPath, String optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(dexPath, new File(optimizedDirectory), libraryPath, parent);
        }
    }

    dexpath为jar或apk文件目录。

    optimizedDirectory为优化dex缓存目录。

    libraryPath包含native lib的目录路径。

    parent父类加载器。

    然后执行的是父类的构造函数:

    super(dexPath, new File(optimizedDirectory), libraryPath, parent);

    BaseDexClassLoader 的构造函数如下:

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

    第一句调用的还是父类的构造函数,也就是ClassLoader的构造函数:

    protected ClassLoader(ClassLoader parentLoader) {
            this(parentLoader, false);
        }
        /*
         * constructor for the BootClassLoader which needs parent to be null.
         */
        ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
           if (parentLoader == null && !nullAllowed) {
                throw new NullPointerException(“parentLoader == null && !nullAllowed”);
          }
          parent = parentLoader;
    }

    该构造函数把传进来的父类加载器赋给了私有变量parent。

    再来看

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

    pathList为该类的私有成员变量,类型为DexPathList,进去DexPathList函数:

    public DexPathList(ClassLoader definingContext, String dexPath,
                String libraryPath, File optimizedDirectory) {
        ………..    
    this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                               suppressedExceptions);
        ………..
    }

    前面是一些对于传入参数的验证,然后调用了makeDexElements。

    private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                                 ArrayList<IOException> suppressedExceptions) {
    ArrayList<Element> elements = new ArrayList<Element>();
            for (File file : files) {
                File zip = null;
                DexFile dex = null;
                String name = file.getName();

                if (name.endsWith(DEX_SUFFIX)) {               //dex文件处理
                    // 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)) {   //apk,jar,zip文件处理
                    zip = file;

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

                if ((zip != null) || (dex != null)) {
                    elements.add(new Element(file, false, zip, dex));
                }
            }

            return elements.toArray(new Element[elements.size()]);
        }
    }

    不管是dex文件,还是apk文件最终加载的都是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);
            }
    }

    如果optimizedDirectory为null就会调用openDexFile(fileName, null, 0);加载文件。

    否则调用DexFile.loadDex(file.getPath(), optimizedPath, 0);

    而这个函数也只是直接调用new DexFile(sourcePathName, outputPathName, flags);

    里面调用的也是openDexFile(sourceName, outputName, flags);

    所以最后都是调用openDexFile,跟进这个函数:

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

    而这个函数调用的是so的openDexFileNative这个函数。打开成功则返回一个cookie。

    接下来就是分析native函数的实现部分了。

    ———-openDexFileNative———-

    代码地址:http://androidxref.com/4.4_r1/xref/dalvik/vm/native/dalvik_system_DexFile.cpp

    static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,
        JValue* pResult)
    {
        ……………
    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”);
        }
        ……………
    }

    这里会根据是否为dex文件或者包含classes.dex文件的jar,分别调用函数dvmRawDexFileOpen和dvmJarFileOpen来处理,最终返回一个DexOrJar的结构。

    首先来看dvmRawDexFileOpen函数的处理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName,
        RawDexFile** ppRawDexFile, bool isBootstrap)
    {
        .................
        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);

        //校验前8个字节的magic是否正确,然后把校验和保存到adler32
        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;
        }
        .................
        //调用函数dexOptCreateEmptyHeader,构造了一个DexOptHeader结构体,写入fd并返回
        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;

           //如果成功生了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();
                // 将dex文件中的内容写入文件的当前位置,也就是从dexOffset的偏移处开始写
                result = copyFileToFile(optFd, dexFd, fileSize) == 0;
                copyWhen = dvmGetRelativeTimeUsec();
            }

            if (result) {
                //对dex文件进行优化
                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);
        }

         //dvmDexFileOpenFromFd这个函数最主要在这里干了两件事情
         // 1.将优化后得dex文件(也就是odex文件)通过mmap映射到内存中,并通过mprotect修改它的映射内存为只读权限
         // 2.将映射为只读的这块dex数据中的内容全部提取到DexFile这个数据结构中去
        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);
        //填充结构体 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;
    }

    最后成功的话,填充RawDexFile。

    dvmJarFileOpen的代码处理也是差不多的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
        JarFile** ppJarFile, bool isBootstrap)
    {
        ...
        ...
        ...
        //调用函数dexZipOpenArchive来打开zip文件,并缓存到系统内存里
        if (dexZipOpenArchive(fileName, &archive) != 0)
            goto bail;
        archiveOpen = true;
        ...
        //这行代码设置当执行完成后,关闭这个文件句柄
        dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));
        ...
        //优先处理已经优化了的Dex文件
        fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
        ...
        //从压缩包里找到Dex文件,然后打开这个文件
        entry = dexZipFindEntry(&archive, kDexInJarName);
        ...
        //把未经过优化的Dex文件进行优化处理,并输出到指定的文件
        if (odexOutputName == NULL) {
                    cachedName = dexOptGenerateCacheFileName(fileName,
                                    kDexInJarName);
        }
        ...
        //创建缓存的优化文件
        fd = dvmOpenCachedDexFile(fileName, cachedName,
                        dexGetZipEntryModTime(&archive, entry),
                        dexGetZipEntryCrc32(&archive, entry),
                        isBootstrap, &newFile, /*createIfMissing=*/true);
        ...
        //调用函数dexZipExtractEntryToFile从压缩包里解压文件出来
        if (result) {
                        startWhen = dvmGetRelativeTimeUsec();
                        result = dexZipExtractEntryToFile(&archive, entry, fd) == 0;
                        extractWhen = dvmGetRelativeTimeUsec();
                     }
        ...
        //调用函数dvmOptimizeDexFile对Dex文件进行优化处理
        if (result) {
                        result = dvmOptimizeDexFile(fd, dexOffset,
                                    dexGetZipEntryUncompLen(&archive, entry),
                                    fileName,
                                    dexGetZipEntryModTime(&archive, entry),
                                    dexGetZipEntryCrc32(&archive, entry),
                                    isBootstrap);
                    }
        ...
        //调用函数dvmDexFileOpenFromFd来缓存dex文件
        //并分析文件的内容。比如标记是否优化的文件,通过签名检查Dex文件是否合法
        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;
        }
        ...
         //设置一些相关信息返回前面的函数处理。
        *ppJarFile = (JarFile*) calloc(1, sizeof(JarFile));
        (*ppJarFile)->archive = archive;
        (*ppJarFile)->cacheFileName = cachedName;
        (*ppJarFile)->pDvmDex = pDvmDex;
        cachedName = NULL;      // don't free it below
        result = 0;
        ...

    }

    最后成功的话,填充JarFile。

     

    参考文章:

    http://bbs.pediy.com/showthread.php?t=199230

    http://0nly3nd.sinaapp.com/?p=688

    http://blog.csdn.net/roland_sun/article/details/47183119

  • 相关阅读:
    [读书笔记] 代码整洁之道(五): 系统
    [读书笔记] 代码整洁之道(四): 类
    [读书笔记] 代码整洁之道(三): 错误处理及边界接口处理
    [读书笔记] 代码整洁之道(二):对象和数据结构
    程序猿的书单
    selenium自动化-java-封装断言
    java环境变量详细配置步骤
    Selenium-java-TestNg-的运行
    quicktest Professional下载地址,无限制使用方法
    常用网站收集
  • 原文地址:https://www.cnblogs.com/csnd/p/11800618.html
Copyright © 2011-2022 走看看