美文网首页Android基础知识Android技术
Android 9.0 ART编译分析(二)-Installd触

Android 9.0 ART编译分析(二)-Installd触

作者: Stan_Z | 来源:发表于2019-08-16 17:57 被阅读99次

    原创内容,转载请注明出处,多谢配合。

    这个通路是经过PMS,最终由installd触发的主apk编译。

    一、Installd介绍

    Installd是Android native层的服务进程,在init阶段通过init.rc对应的配置服务启动的。

    #frameworks/native/cmds/installd/Android.bp
    
    cc_binary {
    ...
        init_rc: ["installd.rc"],
    }
    
    #frameworks/native/cmds/installd/installd.rc
    service installd /system/bin/installd
        class main
    

    Android 中提供了PMS来进行包管理工作,对上层交付的内容包括应用安装、卸载、以及Pakcage信息的管理。但是这个过程中牵涉到的目录创建、安装包copy、dex优化等内容,最终是交给installd去执行的,为什么?因为从上面我们知道了installd是由init孵化的(拥有root权限),而PMS是由zygote孵化的(只拥有system权限),显然installd拥有的权限远远高于PMS,因此它主要负责处理需要root权限的操作。

    二、通路介绍

    通过Installd触发dex2oat执行编译,上层都是通过PMS来执行的。简单打几个调用栈看看:

    1) install
    08-15 13:17:22.435 1548 1688 I PackageManager.DexOptimizer: ZHT Running dexopt (dexoptNeeded=1) on: /data/app/com.ss.android.article.news-UcwHwLxdQxcoHjQhpmsTkA==/base.apk pkg=com.ss.android.article.news isa=arm dexoptFlags=boot_complete,public,enable_hidden_api_checks targetFilter=quicken oatDir=/data/app/com.ss.android.article.news-UcwHwLxdQxcoHjQhpmsTkA==/oat classLoaderContext=PCL[/system/framework/org.apache.http.legacy.boot.jar]
    dex2oatpath=
    com.android.server.pm.PackageDexOptimizer.performDexOptLI:253
     com.android.server.pm.PackageDexOptimizer.performDexOpt:149
     com.android.server.pm.PackageManagerService.installPackageLI:18215
    com.android.server.pm.PackageManagerService.installPackageTracedLI:17635
     com.android.server.pm.PackageManagerService.access$3300:407
     com.android.server.pm.PackageManagerService$10.run:15465
    android.os.Handler.handleCallback:873
    android.os.Handler.dispatchMessage:99
    android.os.Looper.loop:201
    android.os.HandlerThread.run:65
    com.android.server.ServiceThread.run:45
    
    2) post boot
    08-15 13:29:22.200  1450  6499 I PackageManager.DexOptimizer: ZHT Running dexopt (dexoptNeeded=1) on: /data/app/com.ss.android.article.news-UcwHwLxdQxcoHjQhpmsTkA==/base.apk pkg=com.ss.android.article.news isa=arm dexoptFlags=boot_complete,profile_guided,enable_hidden_api_checks targetFilter=verify oatDir=/data/app/com.ss.android.article.news-UcwHwLxdQxcoHjQhpmsTkA==/oat classLoaderContext=PCL[/system/framework/org.apache.http.legacy.boot.jar] dex2oatpath=
    com.android.server.pm.PackageDexOptimizer.performDexOptLI:253
    com.android.server.pm.PackageDexOptimizer.performDexOpt:149
    com.android.server.pm.PackageManagerService.performDexOptInternalWithDependenciesLI:9723
    com.android.server.pm.PackageManagerService.performDexOptInternal:9674
    com.android.server.pm.PackageManagerService.performDexOptTraced:9652
    com.android.server.pm.PackageManagerService.performDexOptWithStatus:9637
    com.android.server.pm.BackgroundDexOptService.postBootUpdate:233
    com.android.server.pm.BackgroundDexOptService.access$000:52
    com.android.server.pm.BackgroundDexOptService$1.run:183
    

    另外oat 、idle就不一一例举了,获取打印log比较麻烦一点,但是基本上流程也差不多,最终都汇集到com.android.server.pm.PackageDexOptimizer.dexOptPath()方法

    三、编译流程
    frameworks/base/services/core/java/com/android/server/pm/PackageDexOptimizer.java
     
    private int dexOptPath(PackageParser.Package pkg, String path, String isa,
     String compilerFilter, boolean profileUpdated, String classLoaderContext,
     int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade,
     String profileName, String dexMetadataPath, int compilationReason) {
        //判断是否主要做dex2oat编译
        int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext,
     profileUpdated, downgrade);
     if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) {
            return DEX_OPT_SKIPPED;
     }
     ...
     //通过installd走dex2oat编译
     mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
     compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo,
     false /* downgrade*/, pkg.applicationInfo.targetSdkVersion,
     profileName, dexMetadataPath,
     getAugmentedReasonName(compilationReason, dexMetadataPath != null));
    ...
    }
    

    这个方法主要就干了两件事:判断是否需要做dex2oat 和 通过Installer binder call给installd(7.0及之前它与Installer是进行socket通信) 去执行dexopt操作。

    下面先来看一张整体流程图:

    经PMS由Installd触发的dex2oat编译流程
    2.1 判断是否需要做dex2oat的逻辑:

    从时序图看,最终逻辑在oat_file_assisatant.cc
    这里不跟代码了,提炼下核心逻辑要点:

    1)是否需要编译的类型分类:
    class OatFileAssistant {
    //是否需要编译
    enum DexOptNeeded {
     kNoDexOptNeeded = 0, //已经编译过,不需要再编译
     kDex2OatFromScratch = 1, //有dex文件,但还没编过
     kDex2OatForBootImage = 2,//oat文件不能匹配boot image(系统升级 boot image会变化)
     kDex2OatForFilter = 3,//oat文件不能匹配compiler filter
     kDex2OatForRelocation = 4, //还是oat文件与boot image不匹配,但是没有深刻理解relocation是什么场景
     }
    
     //对应的几种状态
    enum OatStatus {
     kOatCannotOpen, //oat文件不存在
     kOatDexOutOfDate, //oat文件过期,与dex文件不匹配
     kOatBootImageOutOfDate, //对应kDex2OatForBootImage,oat文件与boot image不匹配
     kOatRelocationOutOfDate,//对应kDex2OatForRelocation oat文件与boot image不匹配
     kOatUpToDate,//oat文件与dex文件和 boot image都匹配
     };
    }
    

    核心逻辑:

    art/runtime/oat_file_assistant.cc
     
    int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target,
                                          bool profile_changed,
                                          bool downgrade,
                                          ClassLoaderContext* class_loader_context) {
      OatFileInfo& info = GetBestInfo();//获取OatFileInfo对应实例
      DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target,
                                                        profile_changed,
                                                        downgrade,
                                                        class_loader_context);
      if (info.IsOatLocation() || dexopt_needed == kDex2OatFromScratch) {
        return dexopt_needed;
      }
      return -dexopt_needed;
    }
    

    这里有两个概念需要了解:

    • oat location 与odex location 分别是什么?
      app的安装系统目录data/app和system/app,这个路径下每个应用都会生成一个类似包名+乱码的一个文件夹,里面存放主apk以及编译文件。
      oat location对应的是oat文件夹路径
      odex location对应的是oat/arm or arm64/odex文件路径
      如果有odex优先用odex。

    • 正负数是指的什么?
      正数对应in_odex_path ,负数对应out_oat_path

    2)DexOptNeeded各类型赋值

    这里主要是看看这几个判断类型是在哪赋值的,这样就知道编译的触发条件有哪些了

    if (!oat_file_assistant.IsUpToDate()) { 
     switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/false, /*out*/ &error_msg)) {
    ...
    }
    

    过期逻辑一般是先IsUpToDate判断是否过期,然后MakeUpToDate做过期操作,很明显这个部分还是在oat_file_assistant.cc做的

    art/runtime/oat_file_assistant.cc
    
    bool OatFileAssistant::IsUpToDate() {
      return GetBestInfo().Status() == kOatUpToDate;//是不是已经编过了
    }
    

    没有编过就通过MakeUpToDate来置DexOptNeeded编译类型

    OatFileAssistant::MakeUpToDate(bool profile_changed, std::string* error_msg) {
      CompilerFilter::Filter target;
      if (!GetRuntimeCompilerFilterOption(&target, error_msg)) {
        return kUpdateNotAttempted; //We wanted to update the code, but determined we should not make the attempt.
      }
      OatFileInfo& info = GetBestInfo();
      switch (info.GetDexOptNeeded(target, profile_changed)) { //这里有各种条件来赋值DexOptNeeded,条件跟之前的描述差不多
    
        case kNoDexOptNeeded:
          return kUpdateSucceeded;//We successfully made the code up to date (possibly by doing nothing).
    
        // TODO: For now, don't bother with all the different ways we can call
     // dex2oat to generate the oat file. Always generate the oat file as if it
     // were kDex2OatFromScratch.
        case kDex2OatFromScratch:
        case kDex2OatForBootImage:
        case kDex2OatForRelocation:
        case kDex2OatForFilter:
          return GenerateOatFileNoChecks(info, target, error_msg);//mark the odex file has changed and we should try to reload.
    
      }
      UNREACHABLE();
    }
    

    主要赋值在GetBestInfo()

    OatFileAssistant::OatFileInfo& OatFileAssistant::GetBestInfo() {
      // TODO(calin): Document the side effects of class loading when
      // running dalvikvm command line.
      if (dex_parent_writable_) {
        // If the parent of the dex file is writable it means that we can
        // create the odex file. In this case we unconditionally pick the odex
        // as the best oat file. This corresponds to the regular use case when
        // apps gets installed or when they load private, secondary dex file.
        // For apps on the system partition the odex location will not be
        // writable and thus the oat location might be more up to date.
        return odex_;
      }
    
      // We cannot write to the odex location. This must be a system app.
    
      // If the oat location is usable take it.
      if (oat_.IsUseable()) {
        return oat_;
      }
    
      // The oat file is not usable but the odex file might be up to date.
      // This is an indication that we are dealing with an up to date prebuilt
      // (that doesn't need relocation).
      if (odex_.Status() == kOatUpToDate) {
        return odex_;
      }
    
      // The oat file is not usable and the odex file is not up to date.
      // However we have access to the original dex file which means we can make
      // the oat location up to date.
      if (HasOriginalDexFiles()) {
        return oat_;
      }
    
      // We got into the worst situation here:
      // - the oat location is not usable
      // - the prebuild odex location is not up to date
      // - and we don't have the original dex file anymore (stripped).
      // Pick the odex if it exists, or the oat if not.
      return (odex_.Status() == kOatCannotOpen) ? oat_ : odex_;
    }
    

    这里注释也很明显,不赘述了。

    2.2 installd执行dexopt

    8.0之后除了socket换成了binder call ,另外调用也发送了变化,也不像7.0的时候在installd.cpp中通过cmds命令对应depot了 { "dexopt", , do_dexopt },现在操作是在dexopt.cpp中进行。

    frameworks/native/cmds/installd/dexopt.cpp
    int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* instruction_set,
     int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter,
     const char* volume_uuid, const char* shared_libraries, const char* se_info) {
     ...
     run_dex2oat(input_fd.get(),
                out_oat_fd.get(),
                in_vdex_fd.get(),
                out_vdex_fd.get(),
                image_fd.get(),
                dex_path,
                out_oat_path,
                swap_fd.get(),
                instruction_set,
                compiler_filter,
                debuggable,
                boot_complete,
                reference_profile_fd.get(),
                shared_libraries);
    
     ...
     return 0;
    }
    

    这里主要就是向dex2oat可执行文件传参并执行编译操作。

    相关文章

      网友评论

        本文标题:Android 9.0 ART编译分析(二)-Installd触

        本文链接:https://www.haomeiwen.com/subject/xqbqsctx.html