Android 9.0 ART編譯分析(二)-Installd觸發(fā)dex2oat編譯流程

原創(chuàng)內(nèi)容,轉(zhuǎn)載請注明出處,多謝配合。

這個通路是經(jīng)過PMS,最終由installd觸發(fā)的主apk編譯。

一、Installd介紹

Installd是Android native層的服務(wù)進程,在init階段通過init.rc對應(yīng)的配置服務(wù)啟動的。

#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來進行包管理工作,對上層交付的內(nèi)容包括應(yīng)用安裝、卸載、以及Pakcage信息的管理。但是這個過程中牽涉到的目錄創(chuàng)建、安裝包copy、dex優(yōu)化等內(nèi)容,最終是交給installd去執(zhí)行的,為什么?因為從上面我們知道了installd是由init孵化的(擁有root權(quán)限),而PMS是由zygote孵化的(只擁有system權(quán)限),顯然installd擁有的權(quán)限遠遠高于PMS,因此它主要負責處理需要root權(quán)限的操作。

二、通路介紹

通過Installd觸發(fā)dex2oat執(zhí)行編譯,上層都是通過PMS來執(zhí)行的。簡單打幾個調(diào)用??纯矗?/p>

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通信) 去執(zhí)行dexopt操作。

下面先來看一張整體流程圖:

經(jīng)PMS由Installd觸發(fā)的dex2oat編譯流程
2.1 判斷是否需要做dex2oat的邏輯:

從時序圖看,最終邏輯在oat_file_assisatant.cc
這里不跟代碼了,提煉下核心邏輯要點:

1)是否需要編譯的類型分類:
class OatFileAssistant {
//是否需要編譯
enum DexOptNeeded {
 kNoDexOptNeeded = 0, //已經(jīng)編譯過,不需要再編譯
 kDex2OatFromScratch = 1, //有dex文件,但還沒編過
 kDex2OatForBootImage = 2,//oat文件不能匹配boot image(系統(tǒng)升級 boot image會變化)
 kDex2OatForFilter = 3,//oat文件不能匹配compiler filter
 kDex2OatForRelocation = 4, //還是oat文件與boot image不匹配,但是沒有深刻理解relocation是什么場景
 }

 //對應(yīng)的幾種狀態(tài)
enum OatStatus {
 kOatCannotOpen, //oat文件不存在
 kOatDexOutOfDate, //oat文件過期,與dex文件不匹配
 kOatBootImageOutOfDate, //對應(yīng)kDex2OatForBootImage,oat文件與boot image不匹配
 kOatRelocationOutOfDate,//對應(yīng)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對應(yīng)實例
  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的安裝系統(tǒng)目錄data/app和system/app,這個路徑下每個應(yīng)用都會生成一個類似包名+亂碼的一個文件夾,里面存放主apk以及編譯文件。
    oat location對應(yīng)的是oat文件夾路徑
    odex location對應(yīng)的是oat/arm or arm64/odex文件路徑
    如果有odex優(yōu)先用odex。

  • 正負數(shù)是指的什么?
    正數(shù)對應(yīng)in_odex_path ,負數(shù)對應(yīng)out_oat_path

2)DexOptNeeded各類型賦值

這里主要是看看這幾個判斷類型是在哪賦值的,這樣就知道編譯的觸發(fā)條件有哪些了

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;//是不是已經(jīng)編過了
}

沒有編過就通過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執(zhí)行dexopt

8.0之后除了socket換成了binder call ,另外調(diào)用也發(fā)送了變化,也不像7.0的時候在installd.cpp中通過cmds命令對應(yīng)depot了 { "dexopt", , do_dexopt },現(xiàn)在操作是在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可執(zhí)行文件傳參并執(zhí)行編譯操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容