Android应用优化过程分析

最近在看其他的源代码,Android的源码几天没看了。

假期的计划总赶不上变化,尴尬。。。

接着Android应用的安装,看看优化(odex)的过程源码

上篇讲到在installd.c当中,命令参数与cmds中保存的命令进行匹配。

cmds中保存的命令如下

struct cmdinfo {
    const char *name;
    unsigned numargs;
    int (*func)(char **arg, char reply[REPLY_MAX]);
};

struct cmdinfo cmds[] = {
    { "ping",                 0, do_ping },
    { "install",              4, do_install },
    { "dexopt",               3, do_dexopt },
    { "movedex",              2, do_move_dex },
    { "rmdex",                1, do_rm_dex },
    { "remove",               2, do_remove },
    { "rename",               2, do_rename },
    { "fixuid",               3, do_fixuid },
    { "freecache",            1, do_free_cache },
    { "rmcache",              2, do_rm_cache },
    { "getsize",              6, do_get_size },
    { "rmuserdata",           2, do_rm_user_data },
    { "movefiles",            0, do_movefiles },
    { "linklib",              3, do_linklib },
    { "mkuserdata",           3, do_mk_user_data },
    { "rmuser",               1, do_rm_user },
};
其中,dexopt命令就是应用优化命令

do_dexopt函数

static int do_dexopt(char **arg, char reply[REPLY_MAX])
{
        /* apk_path, uid, is_public */
    return dexopt(arg[0], atoi(arg[1]), atoi(arg[2]));
}
dexopt函数位于同目录下的commands.c文件中

int dexopt(const char *apk_path, uid_t uid, int is_public)
{
    struct utimbuf ut;
    struct stat apk_stat, dex_stat;
    char dex_path[PKG_PATH_MAX];
    char dexopt_flags[PROPERTY_VALUE_MAX];
    char *end;
    int res, zip_fd=-1, odex_fd=-1;

        /* Before anything else: is there a .odex file?  If so, we have
         * pre-optimized the apk and there is nothing to do here.
         */
    if (strlen(apk_path) >= (PKG_PATH_MAX - 8)) {
        return -1;
    }

    /* platform-specific flags affecting optimization and verification */
    property_get("dalvik.vm.dexopt-flags", dexopt_flags, "");

    strcpy(dex_path, apk_path);
    end = strrchr(dex_path, '.');
    if (end != NULL) {
        strcpy(end, ".odex");
        if (stat(dex_path, &dex_stat) == 0) {
            return 0;
        }
    }

    if (create_cache_path(dex_path, apk_path)) {
        return -1;
    }

    memset(&apk_stat, 0, sizeof(apk_stat));
    stat(apk_path, &apk_stat);

    zip_fd = open(apk_path, O_RDONLY, 0);
    if (zip_fd < 0) {
        ALOGE("dexopt cannot open '%s' for input\n", apk_path);
        return -1;
    }

    unlink(dex_path);
    odex_fd = open(dex_path, O_RDWR | O_CREAT | O_EXCL, 0644);
    if (odex_fd < 0) {
        ALOGE("dexopt cannot open '%s' for output\n", dex_path);
        goto fail;
    }
    if (fchmod(odex_fd,
               S_IRUSR|S_IWUSR|S_IRGRP |
               (is_public ? S_IROTH : 0)) < 0) {
        ALOGE("dexopt cannot chmod '%s'\n", dex_path);
        goto fail;
    }
    if (fchown(odex_fd, AID_SYSTEM, uid) < 0) {
        ALOGE("dexopt cannot chown '%s'\n", dex_path);
        goto fail;
    }

    ALOGV("DexInv: --- BEGIN '%s' ---\n", apk_path);

    pid_t pid;
    pid = fork();
    if (pid == 0) {
        /* child -- drop privileges before continuing */
        if (setgid(uid) != 0) {
            ALOGE("setgid(%d) failed during dexopt\n", uid);
            exit(64);
        }
        if (setuid(uid) != 0) {
            ALOGE("setuid(%d) during dexopt\n", uid);
            exit(65);
        }
        // drop capabilities
        struct __user_cap_header_struct capheader;
        struct __user_cap_data_struct capdata[2];
        memset(&capheader, 0, sizeof(capheader));
        memset(&capdata, 0, sizeof(capdata));
        capheader.version = _LINUX_CAPABILITY_VERSION_3;
        if (capset(&capheader, &capdata[0]) < 0) {
            ALOGE("capset failed: %s\n", strerror(errno));
            exit(66);
        }
        if (flock(odex_fd, LOCK_EX | LOCK_NB) != 0) {
            ALOGE("flock(%s) failed: %s\n", dex_path, strerror(errno));
            exit(67);
        }

        run_dexopt(zip_fd, odex_fd, apk_path, dexopt_flags);
        exit(68);   /* only get here on exec failure */
    } else {
        res = wait_dexopt(pid, apk_path);
        if (res != 0) {
            ALOGE("dexopt failed on '%s' res = %d\n", dex_path, res);
            goto fail;
        }
    }

    ut.actime = apk_stat.st_atime;
    ut.modtime = apk_stat.st_mtime;
    utime(dex_path, &ut);
    
    close(odex_fd);
    close(zip_fd);
    return 0;

fail:
    if (odex_fd >= 0) {
        close(odex_fd);
        unlink(dex_path);
    }
    if (zip_fd >= 0) {
        close(zip_fd);
    }
    return -1;
}
构造路径,打开原文件,打开(没有就创建)odex文件,然后fork一个进程执行优化

主要的优化在run_dexopt函数中

static void run_dexopt(int zip_fd, int odex_fd, const char* input_file_name,
    const char* dexopt_flags)
{
    static const char* DEX_OPT_BIN = "/system/bin/dexopt";
    static const int MAX_INT_LEN = 12;      // '-'+10dig+'\0' -OR- 0x+8dig
    char zip_num[MAX_INT_LEN];
    char odex_num[MAX_INT_LEN];

    sprintf(zip_num, "%d", zip_fd);
    sprintf(odex_num, "%d", odex_fd);

    execl(DEX_OPT_BIN, DEX_OPT_BIN, "--zip", zip_num, odex_num, input_file_name,
        dexopt_flags, (char*) NULL);
    ALOGE("execl(%s) failed: %s\n", DEX_OPT_BIN, strerror(errno));
}
用execl执行/system/bin/dexopt

/*
 * Main entry point.  Decide where to go.
 */
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);
    }

    fprintf(stderr,
        "Usage:\n\n"
        "Short version: Don't use this.\n\n"
        "Slightly longer version: This system-internal tool is used to\n"
        "produce optimized dex files. See the source code for details.\n");

    return 1;
}
根据"--zip",执行fromZip

static int fromZip(int argc, char* const argv[])
{
    int result = -1;
    int zipFd, cacheFd;
    const char* zipName;
    char* bcpCopy = NULL;
    const char* dexoptFlags;

    if (argc != 6) {
        ALOGE("Wrong number of args for --zip (found %d)", argc);
        goto bail;
    }

    /* skip "--zip" */
    argc--;
    argv++;

    GET_ARG(zipFd, strtol, "bad zip fd");
    GET_ARG(cacheFd, strtol, "bad cache fd");
    zipName = *++argv;
    --argc;
    dexoptFlags = *++argv;
    --argc;

    result = processZipFile(zipFd, cacheFd, zipName, dexoptFlags);

bail:
    return result;
}
GET_ARG是个同文件下的宏定义

/* advance to the next arg and extract it */
#define GET_ARG(_var, _func, _msg)                                          \
    {                                                                       \
        char* endp;                                                         \
        (_var) = _func(*++argv, &endp, 0);                                  \
        if (*endp != '\0') {                                                \
            ALOGE("%s '%s'", _msg, *argv);                                   \
            goto bail;                                                      \
        }                                                                   \
        --argc;                                                             \
    }
函数的最后,也是核心,调用processZipFile

/*
 * Common functionality for normal device-side processing as well as
 * preoptimization.
 */
static int processZipFile(int zipFd, int cacheFd, const char* zipName,
        const char *dexoptFlags)
{
    char* bcpCopy = NULL;

    /*
     * Check to see if this is a bootstrap class entry. If so, truncate
     * the path.
     */
    const char* bcp = getenv("BOOTCLASSPATH");
    if (bcp == NULL) {
        ALOGE("DexOptZ: BOOTCLASSPATH not set");
        return -1;
    }

    bool isBootstrap = false;
    const char* match = strstr(bcp, zipName);
    if (match != NULL) {
        /*
         * TODO: we have a partial string match, but that doesn't mean
         * we've matched an entire path component. We should make sure
         * that we're matching on the full zipName, and if not we
         * should re-do the strstr starting at (match+1).
         *
         * The scenario would be a bootclasspath with something like
         * "/system/framework/core.jar" while we're trying to optimize
         * "/framework/core.jar". Not very likely since all paths are
         * absolute and end with ".jar", but not impossible.
         */
        int matchOffset = match - bcp;
        if (matchOffset > 0 && bcp[matchOffset-1] == ':')
            matchOffset--;
        ALOGV("DexOptZ: found '%s' in bootclasspath, cutting off at %d",
            zipName, matchOffset);
        bcpCopy = strdup(bcp);
        bcpCopy[matchOffset] = '\0';

        bcp = bcpCopy;
        ALOGD("DexOptZ: truncated BOOTCLASSPATH to '%s'", bcp);
        isBootstrap = true;
    }

    int result = extractAndProcessZip(zipFd, cacheFd, zipName, isBootstrap,
            bcp, dexoptFlags);

    free(bcpCopy);
    return result;
}
processZipFile进行包装验证后调用extractAndProcessZip

/*
 * Extract "classes.dex" from zipFd into "cacheFd", leaving a little space
 * up front for the DEX optimization header.
 */
static int extractAndProcessZip(int zipFd, int cacheFd,
    const char* debugFileName, bool isBootstrap, const char* bootClassPath,
    const char* dexoptFlagStr)
{
    ZipArchive zippy;
    ZipEntry zipEntry;
    size_t uncompLen;
    long modWhen, crc32;
    off_t dexOffset;
    int err;
    int result = -1;
    int dexoptFlags = 0;        /* bit flags, from enum DexoptFlags */
    DexClassVerifyMode verifyMode = VERIFY_MODE_ALL;
    DexOptimizerMode dexOptMode = OPTIMIZE_MODE_VERIFIED;

    memset(&zippy, 0, sizeof(zippy));

    /* make sure we're still at the start of an empty file */
    if (lseek(cacheFd, 0, SEEK_END) != 0) {
        ALOGE("DexOptZ: new cache file '%s' is not empty", debugFileName);
        goto bail;
    }

    /*
     * Write a skeletal DEX optimization header.  We want the classes.dex
     * to come just after it.
     */
    err = dexOptCreateEmptyHeader(cacheFd);
    if (err != 0)
        goto bail;

    /* record the file position so we can get back here later */
    dexOffset = lseek(cacheFd, 0, SEEK_CUR);
    if (dexOffset < 0)
        goto bail;

    /*
     * Open the zip archive, find the DEX entry.
     */
    if (dexZipPrepArchive(zipFd, debugFileName, &zippy) != 0) {
        ALOGW("DexOptZ: unable to open zip archive '%s'", debugFileName);
        goto bail;
    }

    zipEntry = dexZipFindEntry(&zippy, kClassesDex);
    if (zipEntry == NULL) {
        ALOGW("DexOptZ: zip archive '%s' does not include %s",
            debugFileName, kClassesDex);
        goto bail;
    }

    /*
     * Extract some info about the zip entry.
     */
    if (dexZipGetEntryInfo(&zippy, zipEntry, NULL, &uncompLen, NULL, NULL,
            &modWhen, &crc32) != 0)
    {
        ALOGW("DexOptZ: zip archive GetEntryInfo failed on %s", debugFileName);
        goto bail;
    }

    uncompLen = uncompLen;
    modWhen = modWhen;
    crc32 = crc32;

    /*
     * Extract the DEX data into the cache file at the current offset.
     */
    if (dexZipExtractEntryToFile(&zippy, zipEntry, cacheFd) != 0) {
        ALOGW("DexOptZ: extraction of %s from %s failed",
            kClassesDex, debugFileName);
        goto bail;
    }

    /* Parse the options. */
    if (dexoptFlagStr[0] != '\0') {
        const char* opc;
        const char* val;

        opc = strstr(dexoptFlagStr, "v=");      /* verification */
        if (opc != NULL) {
            switch (*(opc+2)) {
            case 'n':   verifyMode = VERIFY_MODE_NONE;          break;
            case 'r':   verifyMode = VERIFY_MODE_REMOTE;        break;
            case 'a':   verifyMode = VERIFY_MODE_ALL;           break;
            default:                                            break;
            }
        }

        opc = strstr(dexoptFlagStr, "o=");      /* optimization */
        if (opc != NULL) {
            switch (*(opc+2)) {
            case 'n':   dexOptMode = OPTIMIZE_MODE_NONE;        break;
            case 'v':   dexOptMode = OPTIMIZE_MODE_VERIFIED;    break;
            case 'a':   dexOptMode = OPTIMIZE_MODE_ALL;         break;
            case 'f':   dexOptMode = OPTIMIZE_MODE_FULL;        break;
            default:                                            break;
            }
        }

        opc = strstr(dexoptFlagStr, "m=y");     /* register map */
        if (opc != NULL) {
            dexoptFlags |= DEXOPT_GEN_REGISTER_MAPS;
        }

        opc = strstr(dexoptFlagStr, "u=");      /* uniprocessor target */
        if (opc != NULL) {
            switch (*(opc+2)) {
            case 'y':   dexoptFlags |= DEXOPT_UNIPROCESSOR;     break;
            case 'n':   dexoptFlags |= DEXOPT_SMP;              break;
            default:                                            break;
            }
        }
    }

    /*
     * Prep the VM and perform the optimization.
     */

    if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode,
            dexoptFlags) != 0)
    {
        ALOGE("DexOptZ: VM init failed");
        goto bail;
    }

    //vmStarted = 1;

    /* do the optimization */
    if (!dvmContinueOptimization(cacheFd, dexOffset, uncompLen, debugFileName,
            modWhen, crc32, isBootstrap))
    {
        ALOGE("Optimization failed");
        goto bail;
    }

    /* we don't shut the VM down -- process is about to exit */

    result = 0;

bail:
    dexZipCloseArchive(&zippy);
    return result;
}
这个函数可以很明显的看出来分为几段,首先构造一个空文件和空文件头,将原来文件中的部分数据拷贝到空文件中。

其次,解析设置参数(Parse the options)

最后准备和执行优化,dvmPrepForDexOpt和dvmContinueOptimization函数

重点看后一个dvmContinueOptimization函数

该函数位于dalvik\vm\analysis\DexPrepare.cpp中

函数很长,分段来看

bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,
    const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
{
    DexClassLookup* pClassLookup = NULL;
    RegisterMapBuilder* pRegMapBuilder = NULL;

    assert(gDvm.optimizing);

    ALOGV("Continuing optimization (%s, isb=%d)", fileName, isBootstrap);

    assert(dexOffset >= 0);

    /* quick test so we don't blow up on empty file */
    if (dexLength < (int) sizeof(DexHeader)) {
        ALOGE("too small to be DEX");
        return false;
    }
    if (dexOffset < (int) sizeof(DexOptHeader)) {
        ALOGE("not enough room for opt header");
        return false;
    }

    bool result = false;
声明变量,检查长度

    /*
     * Drop this into a global so we don't have to pass it around.  We could
     * also add a field to DexFile, but since it only pertains to DEX
     * creation that probably doesn't make sense.
     */
    gDvm.optimizingBootstrapClass = isBootstrap;

    {
        /*
         * Map the entire file (so we don't have to worry about page
         * alignment).  The expectation is that the output file contains
         * our DEX data plus room for a small header.
         */
        bool success;
        void* mapAddr;
        mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE,
                    MAP_SHARED, fd, 0);
        if (mapAddr == MAP_FAILED) {
            ALOGE("unable to mmap DEX cache: %s", strerror(errno));
            goto bail;
        }

        bool doVerify, doOpt;
        if (gDvm.classVerifyMode == VERIFY_MODE_NONE) {
            doVerify = false;
        } else if (gDvm.classVerifyMode == VERIFY_MODE_REMOTE) {
            doVerify = !gDvm.optimizingBootstrapClass;
        } else /*if (gDvm.classVerifyMode == VERIFY_MODE_ALL)*/ {
            doVerify = true;
        }

        if (gDvm.dexOptMode == OPTIMIZE_MODE_NONE) {
            doOpt = false;
        } else if (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED ||
                   gDvm.dexOptMode == OPTIMIZE_MODE_FULL) {
            doOpt = doVerify;
        } else /*if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)*/ {
            doOpt = true;
        }
将文件内容mmap到mapAddr,根据从全局变量gDvm传递的信息设置选项

        /*
         * Rewrite the file.  Byte reordering, structure realigning,
         * class verification, and bytecode optimization are all performed
         * here.
         *
         * In theory the file could change size and bits could shift around.
         * In practice this would be annoying to deal with, so the file
         * layout is designed so that it can always be rewritten in place.
         *
         * This creates the class lookup table as part of doing the processing.
         */
        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) {
                ALOGE("Unable to create DexFile");
                success = false;
            } else {
                /*
                 * If configured to do so, generate register map output
                 * for all verified classes.  The register maps were
                 * generated during verification, and will now be serialized.
                 */
                if (gDvm.generateRegisterMaps) {
                    pRegMapBuilder = dvmGenerateRegisterMaps(pDvmDex);
                    if (pRegMapBuilder == NULL) {
                        ALOGE("Failed generating register maps");
                        success = false;
                    }
                }

                DexHeader* pHeader = (DexHeader*)pDvmDex->pHeader;
                updateChecksum(dexAddr, dexLength, pHeader);

                dvmDexFileFree(pDvmDex);
            }
        }
从原文注释就可以看到,这段很关键,文件重写,比特位、结构重排,类核实,字节码优化都在这部分

        /* unmap the read-write version, forcing writes to disk */
        if (msync(mapAddr, dexOffset + dexLength, MS_SYNC) != 0) {
            ALOGW("msync failed: %s", strerror(errno));
            // weird, but keep going
        }
#if 1
        /*
         * This causes clean shutdown to fail, because we have loaded classes
         * that point into it.  For the optimizer this isn't a problem,
         * because it's more efficient for the process to simply exit.
         * Exclude this code when doing clean shutdown for valgrind.
         */
        if (munmap(mapAddr, dexOffset + dexLength) != 0) {
            ALOGE("munmap failed: %s", strerror(errno));
            goto bail;
        }
#endif

        if (!success)
            goto bail;
    }
这段没什么好说的

    /* get start offset, and adjust deps start for 64-bit alignment */
    off_t depsOffset, optOffset, endOffset, adjOffset;
    int depsLength, optLength;
    u4 optChecksum;

    depsOffset = lseek(fd, 0, SEEK_END);
    if (depsOffset < 0) {
        ALOGE("lseek to EOF failed: %s", strerror(errno));
        goto bail;
    }
    adjOffset = (depsOffset + 7) & ~(0x07);
    if (adjOffset != depsOffset) {
        ALOGV("Adjusting deps start from %d to %d",
            (int) depsOffset, (int) adjOffset);
        depsOffset = adjOffset;
        lseek(fd, depsOffset, SEEK_SET);
    }

    /*
     * Append the dependency list.
     */
    if (writeDependencies(fd, modWhen, crc) != 0) {
        ALOGW("Failed writing dependencies");
        goto bail;
    }

    /* compute deps length, then adjust opt start for 64-bit alignment */
    optOffset = lseek(fd, 0, SEEK_END);
    depsLength = optOffset - depsOffset;

    adjOffset = (optOffset + 7) & ~(0x07);
    if (adjOffset != optOffset) {
        ALOGV("Adjusting opt start from %d to %d",
            (int) optOffset, (int) adjOffset);
        optOffset = adjOffset;
        lseek(fd, optOffset, SEEK_SET);
    }

    /*
     * Append any optimized pre-computed data structures.
     */
    if (!writeOptData(fd, pClassLookup, pRegMapBuilder)) {
        ALOGW("Failed writing opt data");
        goto bail;
    }

    endOffset = lseek(fd, 0, SEEK_END);
    optLength = endOffset - optOffset;

    /* compute checksum from start of deps to end of opt area */
    if (!computeFileChecksum(fd, depsOffset,
            (optOffset+optLength) - depsOffset, &optChecksum))
    {
        goto bail;
    }

    /*
     * Output the "opt" header with all values filled in and a correct
     * magic number.
     */
    DexOptHeader optHdr;
    memset(&optHdr, 0xff, sizeof(optHdr));
    memcpy(optHdr.magic, DEX_OPT_MAGIC, 4);
    memcpy(optHdr.magic+4, DEX_OPT_MAGIC_VERS, 4);
    optHdr.dexOffset = (u4) dexOffset;
    optHdr.dexLength = (u4) dexLength;
    optHdr.depsOffset = (u4) depsOffset;
    optHdr.depsLength = (u4) depsLength;
    optHdr.optOffset = (u4) optOffset;
    optHdr.optLength = (u4) optLength;
#if __BYTE_ORDER != __LITTLE_ENDIAN
    optHdr.flags = DEX_OPT_FLAG_BIG;
#else
    optHdr.flags = 0;
#endif
    optHdr.checksum = optChecksum;

    fsync(fd);      /* ensure previous writes go before header is written */

    lseek(fd, 0, SEEK_SET);
    if (sysWriteFully(fd, &optHdr, sizeof(optHdr), "DexOpt opt header") != 0)
        goto bail;

    ALOGV("Successfully wrote DEX header");
    result = true;

    //dvmRegisterMapDumpStats();

bail:
    dvmFreeRegisterMapBuilder(pRegMapBuilder);
    free(pClassLookup);
    return result;
}
这段有几个关键,writeDependencies添加依赖列表,writeOptData写入预计算优化信息(类索引、寄存器映射)

然后对表头进行修改

整个优化后的odex文件详见我老大的博客http://blog.csdn.net/roland_sun/article/details/47183119

最后借我老大的图一用














  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值