apk安装和优化原理

0x00

apk安装的方式有:

1、开机启动时安装

2、通过adb install 或者在手机中点击apk,进行界面安装。

0x01

开机启动后在system_server中调用PackageManagerService.main,随着调用的深入,循环对每个apk都调用scanPackageLI方法,这个函数提取apk的AndroidManifest.xml里面的内容放在PackagemanagerService中,并且安装了apk,还有优化了dex。

安装apk的代码:

 int ret = mInstaller.install(pkgName, useEncryptedFSDir, pkg.applicationInfo.uid,
                            pkg.applicationInfo.uid);

优化dex的代码:

if (performDexOptLI(pkg, forceDex) == DEX_OPT_FAILED) {
                    mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
                    return null;
                }
    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex) {
        boolean performed = false;
        if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0 && mInstaller != null) {
            String path = pkg.mScanPath;
            int ret = 0;
            try {
                if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {
                    ret = mInstaller.dexopt(path, pkg.applicationInfo.uid,
                            !isForwardLocked(pkg));
                    pkg.mDidDexOpt = true;
                    performed = true;
                }
            } catch (FileNotFoundException e) {
                Slog.w(TAG, "Apk not found for dexopt: " + path);
                ret = -1;
            } catch (IOException e) {
                Slog.w(TAG, "IOException reading apk: " + path, e);
                ret = -1;
            } catch (dalvik.system.StaleDexCacheError e) {
                Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e);
                ret = -1;
            } catch (Exception e) {
                Slog.w(TAG, "Exception when doing dexopt : ", e);
                ret = -1;
            }
            if (ret < 0) {
                //error from installer
                return DEX_OPT_FAILED;
            }
        }

        return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
    }

mInstaller.dexopt 通过socket通信 让installd 进程(由init进程起来了)执行do_dexopt-->dexopt-->fork出子进程去执行run_dexopt,安装和优化的调用流程请参考Android安装服务installd源码分析

run_dexopt代码如下:

static void run_dexopt(int zip_fd, int odex_fd, const char* input_file_name,
    const char* dexopt_flags)
{
	//input_file_name为apk的路径
    static const char* DEX_OPT_BIN = "/system/bin/dexopt";
    static const int MAX_INT_LEN = 12;
    char zip_num[MAX_INT_LEN];
    char odex_num[MAX_INT_LEN];
    sprintf(zip_num, "%d", zip_fd);//apk文件句柄
    sprintf(odex_num, "%d", odex_fd);//dex文件句柄
	//调用/system/bin/dexopt工具来优化apk文件
    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));
}

fork出的子线程执行的是/system/bin/dexopt,代码位于dalvik\dexopt\OptMain.c

0x02

执行的是/system/bin/dexopt,实际上就是OptMain.c的main函数。

/*
 * 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);
    }
    ......
    return 1;
}

代码位于dalvik\dexopt\OptMain.c。

由于执行时传入的参数是--zip,所以这里执行fromZip。

static int fromZip(int argc, char* const argv[])
{
    ......

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

bail:
    return result;
}

代码位于dalvik\dexopt\OptMain.c。    
    然后经过processZipFile->extractAndProcessZip->dvmContinueOptimization。

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

    ......

    {
        /*
         * 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) {
            LOGE("unable to mmap DEX cache: %s\n", strerror(errno));
            goto bail;
        }

        ......
        success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,
                    &headerFlags, &pClassLookup);

        if (success) {
            DvmDex* pDvmDex = NULL;
            u1* dexAddr = ((u1*) mapAddr) + dexOffset;

            if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) {
                LOGE("Unable to create DexFile\n");
                success = false;
            } else {
                ......
            }
        }

        ......

        if (!success)
            goto bail;
    }

    ......

    if (writeDependencies(fd, modWhen, crc) != 0) {
        LOGW("Failed writing dependencies\n");
        goto bail;
    }

    ......
    if (!writeOptData(fd, pClassLookup, pRegMapBuilder)) {
        LOGW("Failed writing opt data\n");
        goto bail;
    }

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

    optHdr.flags = headerFlags;
    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;

    LOGV("Successfully wrote DEX header\n");
    result = true;

    //dvmRegisterMapDumpStats();

bail:
    dvmFreeRegisterMapBuilder(pRegMapBuilder);
    free(pClassLookup);
    return result;
}

代码位于dalvik\vm\analysis\DexPrepare.c

dexOffset为odex文件头部大小,dexLength为dex文件长度。首先调用mmap把要优化的dex加载到内存虚拟地址mapAddr,这个dex其实就是位于/data/dalvik-cache/[email protected]。

然后调用rewriteDex函数对目标文件进行优化验证,其主要内容包括:字符顺序调整、字节码替换、字节码验证以及文件结构重新对齐。

然后通过writeDependencies写入依赖库信息,writeOptData写入其他优化信息,包括类索引信息以及寄存器映射关系。

最后修改odex文件的头部内容。

生成odex更为详细的流程请参考Android系统ODEX文件格式解析

此时生成的odex其实就是位于/data/dalvik-cache/[email protected]。

odex结构图如下:

0x03

adb install的安装流程请参考深入理解PackageManagerService。整个安装流程,首先把apk拷贝到/data/local/tmp目录下,在安装的过程中把apk拷贝到/data/app中,最后调用了PackageManagerService的InstallPackagtLI,这个函数调用了installNewPackageLI,installNewPackageLI调用了scanPackageLI,在这个函数里面完成了apk的优化和安装,优化和安装的流程和上面一样。

0x04

本文中讲解了用于PathClassLoader加载/data/dalvik-cache/[email protected]的生成流程。

那么DexClassLoader加载apk的流程是什么呢?

注意PathClassLoader和DexClassLoader的构造函数有不同:

PathClassLoader:

    public PathClassLoader(String path, String libPath, ClassLoader parent) {
        super(parent);

        if (path == null)
            throw new NullPointerException();

        this.path = path;
        this.libPath = libPath;

        mPaths = path.split(":");
        int length = mPaths.length;

        //System.out.println("PathClassLoader: " + mPaths);
        mFiles = new File[length];
        mZips = new ZipFile[length];
        mDexs = new DexFile[length];

        boolean wantDex =
            System.getProperty("android.vm.dexfile", "").equals("true");

        /* open all Zip and DEX files up front */
        for (int i = 0; i < length; i++) {
            //System.out.println("My path is: " + mPaths[i]);
            File pathFile = new File(mPaths[i]);
            mFiles[i] = pathFile;

            if (pathFile.isFile()) {
                try {
                    mZips[i] = new ZipFile(pathFile);
                }
                catch (IOException ioex) {
                    // expecting IOException and ZipException
                    //System.out.println("Failed opening ‘" + pathFile + "‘: " + ioex);
                    //ioex.printStackTrace();
                }
                if (wantDex) {
                    /* we need both DEX and Zip, because dex has no resources */
                    try {
                        mDexs[i] = new DexFile(pathFile);
                    }
                    catch (IOException ioex) {}
                }
            }
        }
        ......
    }

最终调用的是new DexFile(pathFile)。

而DexClassLoader:

    public DexClassLoader(String dexPath, String dexOutputDir, String libPath,
        ClassLoader parent) {

        super(parent);

        if (dexPath == null || dexOutputDir == null)
            throw new NullPointerException();

        mRawDexPath = dexPath;
        mDexOutputPath = dexOutputDir;
        mRawLibPath = libPath;

        String[] dexPathList = mRawDexPath.split(":");
        int length = dexPathList.length;

        //System.out.println("DexClassLoader: " + dexPathList);
        mFiles = new File[length];
        mZips = new ZipFile[length];
        mDexs = new DexFile[length];

        /* open all Zip and DEX files up front */
        for (int i = 0; i < length; i++) {
            //System.out.println("My path is: " + dexPathList[i]);
            File pathFile = new File(dexPathList[i]);
            mFiles[i] = pathFile;

            if (pathFile.isFile()) {
                try {
                    mZips[i] = new ZipFile(pathFile);
                } catch (IOException ioex) {
                    // expecting IOException and ZipException
                    System.out.println("Failed opening ‘" + pathFile
                        + "‘: " + ioex);
                    //ioex.printStackTrace();
                }

                /* we need both DEX and Zip, because dex has no resources */
                try {
                    String outputName =
                        generateOutputName(dexPathList[i], mDexOutputPath);
                    mDexs[i] = DexFile.loadDex(dexPathList[i], outputName, 0);
                } catch (IOException ioex) {
                    // might be a resource-only zip
                    System.out.println("Failed loadDex ‘" + pathFile
                        + "‘: " + ioex);
                }
            } else {
                if (VERBOSE_DEBUG)
                    System.out.println("Not found: " + pathFile.getPath());
            }
        }

        .......
    }

最终调用的是DexFile.loadDex(dexPathList[i], outputName, 0)。

说明DexClassLoader还需要指定一个生成优化后的apk的路径。而PathClassLoader则不需要,因为在安装阶段已经生成了/data/dalvik-cache/[email protected]。

时间: 2024-11-07 11:42:31

apk安装和优化原理的相关文章

深度探究apk安装过程

一.先验知识 0.PcakageaManagerService版本变化 1.概述 2.PackageManagerService服务启动流程 3. PackageManagerService入口 二.四种安装方式 1.系统应用安装2.网络下载应用安装3. ADB工具安装 4.第三方应用安装 三.总结 概述 1.1概述 众所周知,Android应用最终是打包成.apk格式(其实就是一个压缩包),然后安装至手机并运行的.APK即Android Package的缩写. Android系统在启动的过程中

我必须得告诉你的MySQL优化原理3

聊聊MySQL配置. 大多数开发者可能不太会关注MySQL的配置,毕竟在基本配置没有问题的情况下,把更多的精力放在schema设计.索引优化和SQL优化上,是非常务实的策略.这时,如果再花力气去优化配置项,获得的收益通常都比较小.更多的时候,基于安全因素的考量,普通开发者很少能够接触到生产环境的MySQL配置.正是这样,导致开发者(包括我)对MySQL的配置不甚了解,希望本文能帮你更好的了解MySQL配置. 如果让你在某种环境上安装配置MySQL,你会怎么做?安装后,直接copy修改示例配置文件

Oracle的优化原理

先说明一下,ORACLE有一个优化器(Optimizer),ORACLE的优化机理就是从Optimizer开始的. 明确两个概念:Optimizer 对ORACLE的优化方式有两种,一种是基于规则的,我们称为RBO(Rule-Based Optimization),一种是基于代价的CBO(Cost-Based Optimization),我们从字面就可基本理解这两个优化方式的含义,不错,RBO是根据ORACLE的内定规则实现的,比如我在"ORACLE性 能调优原则"中讲到的:索引,索引

APK安装流程概述

pre { background: transparent none repeat scroll 0% 0%; border: 1px solid rgb(0, 0, 0); padding: 0.04cm; direction: ltr; color: rgb(0, 0, 0) } pre.western { font-family: "Liberation Mono", "Courier New", monospace } pre.cjk { font-fami

centos7安装与优化

各位小伙伴,安装过程图片有点问题,我处理一下,马上更新 CentOS-7安装与优化 我这里用7.2的版本,为了后面云计算的兼容性做准备 centos的演变 sysvinit技术      系统第一个启动进程:init,pid=1 串行启动:一次一个,一个一个启动 使用的版本:centos5 init优点:运行非常良好,概念简单清晰.主要依赖于shell脚本 init缺点:1.按照一定顺序执行,启动慢2.容易hang住,fstab与nfs挂载问题 upstart 技术(过度的技术)       串

制作PHP安装程序的原理和步骤56

1.制作PHP安装程序的原理和步骤检查目录或文件的权限----修改或填加配置文件---检查配置文件正 确性---导入数据库----锁定或删除安装文件 原理: 其实PHP程序的安装原理无非就是将数据库结构和 内容导入到 相应的数据库中,从这个过程中重新配置 连接数据库的参数和文件,为 了保证不被别人恶意使用安装文件,当安装完成后需要修改安装文件 .2制作安装用到的PHP函数is_writable(“data/config.php”);is_writable() 检查文件是否可写,用来判断文件权限,

apk安装时把程序附带文件拷贝到手机指定目录下

项目已搞定,今天把.apk文件弄到另外一台非调试手机上用,发现一个问题.因为要画图,所以绘图的点的数据保存在一个.txt的文本文件中,上次直接把它用usb传到指定文件夹下的,但是明显不科学,因为用户下载了你的.apk文件,你却告诉他,还要把这个文本文件拷贝到指定的文件夹下,所以,我就要解决这个问题,就是把文本文件打包在apk文件中,安装.apk时就让创建一个程序文件夹,然后把文本文件拷贝到这个目录文件夹里,用户运行程序,就可以绘图,不用再让他拷贝一份绘图的点的坐标的数据.同理,其实我这里还有设计

通过adb把apk安装到系统分区

通过adb把apk安装到系统分区 以谷歌拼音为例:GooglePinyin1.4.2.apk提取出so文件libjni_googlepinyinime_4.solibjni_googlepinyinime_latinime_4.so bat批处理脚本内容如下:path "D:\Program Files\完美刷机\tools";%path% prompt $Gcd /d "%1"cls adb devices adb shell su -c "mount

求教Android 将APK安装到/system/app的方法

============问题描述============ 我在网上找了很多资料,用代码实现将自己的APK安装到/system/app 项目测试需要,让自己的service 不被第三方软件kill掉 这里有一篇文章http://chongye89.iteye.com/blog/1412488 我试过了,但是没有反应  不知道是不是需要什么地方需要注意 望高手指点 或者有什么更好的办法实现我想要的效果 谢谢了 ============解决方案1============ adb shell ok的话