apk安装和优化原理

hmpd6104 8年前

来自: http://blog.csdn.net/jltxgcy/article/details/50550420


    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/xxx@classes.dex。

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

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

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

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

    此时生成的odex其实就是位于/data/dalvik-cache/xxx@classes.dex。

    odex结构图如下:


    
    0x03

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


    0x04

    本文中讲解了用于PathClassLoader加载/data/dalvik-cache/xxx@classes.dex的生成流程。

    那么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/xxx@classes.dex