ART 服務設定

開始前,請先參閱 ART 服務的概略說明。

從 Android 14 開始,應用程式的裝置端 AOT 編譯 (又稱 dexopt) 作業會由 ART 服務處理。ART 服務是 ART 模組的一部分,您可以透過系統屬性和 API 自訂這項服務。

系統屬性

ART 服務支援所有相關的 dex2oat 選項

此外,ART 服務也支援下列系統屬性:

pm.dexopt.<reason>

這是一組系統屬性,可決定「Dexopt 案例」中所有預先定義的「編譯原因」的預設編譯器篩選器。

詳情請參閱「編譯器篩選器」。

標準預設值如下:

pm.dexopt.first-boot=verify
pm.dexopt.boot-after-ota=verify
pm.dexopt.boot-after-mainline-update=verify
pm.dexopt.bg-dexopt=speed-profile
pm.dexopt.inactive=verify
pm.dexopt.cmdline=verify

pm.dexopt.shared (預設值:speed)

這是其他應用程式所用應用程式的回溯編譯器篩選器。

原則上,ART 服務會盡可能對所有應用程式執行設定檔導向編譯 (speed-profile),通常是在背景 dexopt 期間。不過,有些應用程式會由其他應用程式使用 (透過 <uses-library> 或使用 Context#createPackageContext 透過 CONTEXT_INCLUDE_CODE 動態載入)。基於隱私權考量,這類應用程式無法使用本機設定檔。

如果這類應用程式要求以設定檔導向編譯,ART 服務會先嘗試使用雲端設定檔。如果雲端設定檔不存在,ART 服務會改用 pm.dexopt.shared 指定的編譯器篩選器。

如果要求的編譯並非以設定檔為依據,這項屬性就不會生效。

pm.dexopt.<reason>.concurrency (預設值:1)

這是指因特定預先定義的編譯原因 (first-bootboot-after-otaboot-after-mainline-updatebg-dexopt) 而呼叫 dex2oat 的次數。

請注意,這個選項的效果會與 dex2oat 資源用量選項 (dalvik.vm.*dex2oat-threadsdalvik.vm.*dex2oat-cpu-set 和工作設定檔) 合併:

  • dalvik.vm.*dex2oat-threads 會控管每次 dex2oat 呼叫的執行緒數量,而 pm.dexopt.<reason>.concurrency 則會控管 dex2oat 呼叫次數。也就是說,並行執行緒數量上限是這兩項系統屬性的乘積。
  • dalvik.vm.*dex2oat-cpu-set 和工作設定檔一律會限制 CPU 核心用量,無論並行執行緒數量上限為何 (如上所述)。

無論 dalvik.vm.*dex2oat-threads 為何,單一 dex2oat 叫用可能無法充分運用所有 CPU 核心。因此,增加 dex2oat 呼叫次數 (pm.dexopt.<reason>.concurrency) 可更有效運用 CPU 核心,加快 dexopt 整體進度。這在開機期間特別實用。

不過,如果 dex2oat 呼叫次數過多,裝置可能會耗盡記憶體,即使將 dalvik.vm.dex2oat-swap 設為 true 以允許使用交換檔,也可能無法避免。過多的叫用也可能導致不必要的環境切換。因此,請務必針對每項產品仔細調整這個數字。

pm.dexopt.downgrade_after_inactive_days (預設值:未設定)

如果設定這個選項,ART 服務只會對過去指定天數內使用的應用程式執行 dexopt。

此外,如果儲存空間即將不足,ART 服務會在背景 dexopt 期間,將最近一段時間內未使用的應用程式編譯器篩選器降級,以釋出空間。編譯器會因此顯示 inactive, 而編譯器篩選器則由 pm.dexopt.inactive 決定。觸發這項功能的空間用量門檻為「儲存空間管理工具」的空間不足門檻 (可透過全域設定 sys_storage_threshold_percentagesys_storage_threshold_max_bytes 設定,預設為 500MB) 加上 500MB。

如果您透過 ArtManagerLocal#setBatchDexoptStartCallback 自訂套件清單,BatchDexoptStartCallbackbg-dexopt 提供的清單中的套件絕不會降級。

pm.dexopt.disable_bg_dexopt (預設值:false)

這項功能僅供測試,這可防止 ART 服務排定背景 dexopt 工作。

如果背景 dexopt 工作已排定但尚未執行,這個選項不會有任何作用。也就是說,工作仍會執行。

建議的指令順序如下,可防止執行背景 dexopt 工作:

setprop pm.dexopt.disable_bg_dexopt true
pm bg-dexopt-job --disable

第一行會防止系統排定背景 dexopt 工作 (如果尚未排定)。第二行會取消排定背景 dexopt 工作 (如果已排定),並立即取消執行中的背景 dexopt 工作。

ART 服務 API

ART 服務會公開 Java API,供您自訂。這些 API 定義於 ArtManagerLocal。如需用法,請參閱 art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java 中的 Javadoc (Android 14 來源未發布的開發來源)。

ArtManagerLocalLocalManagerRegistry 持有的單例。輔助函式 com.android.server.pm.DexOptHelper#getArtManagerLocal 可協助您取得這項資訊。

import static com.android.server.pm.DexOptHelper.getArtManagerLocal;

大多數 API 都需要 PackageManagerLocal.FilteredSnapshot 執行個體,其中包含所有應用程式的資訊。您可以呼叫 PackageManagerLocal#withFilteredSnapshot 取得,其中 PackageManagerLocal 也是 LocalManagerRegistry 持有的單例項,可從 com.android.server.pm.PackageManagerServiceUtils#getPackageManagerLocal 取得。

import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;

以下列舉幾項 API 的常見用途。

觸發應用程式的 dexopt

您隨時可以呼叫 ArtManagerLocal#dexoptPackage,為任何應用程式觸發 dexopt。

try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
  getArtManagerLocal().dexoptPackage(
      snapshot,
      "com.google.android.calculator",
      new DexoptParams.Builder(ReasonMapping.REASON_INSTALL).build());
}

您也可以傳遞自己的 dexopt 原因。如果這麼做,就必須明確設定優先順序類別和編譯器篩選器。

try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
  getArtManagerLocal().dexoptPackage(
      snapshot,
      "com.google.android.calculator",
      new DexoptParams.Builder("my-reason")
          .setCompilerFilter("speed-profile")
          .setPriorityClass(ArtFlags.PRIORITY_BACKGROUND)
          .build());
}

取消 dexopt

如果作業是由 dexoptPackage 呼叫啟動,您可以傳遞取消訊號,以便在某個時間點取消作業。非同步執行 dexopt 時,這項功能會很實用。

Executor executor = ...;  // Your asynchronous executor here.
var cancellationSignal = new CancellationSignal();
executor.execute(() -> {
  try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
    getArtManagerLocal().dexoptPackage(
        snapshot,
        "com.google.android.calculator",
        new DexoptParams.Builder(ReasonMapping.REASON_INSTALL).build(),
        cancellationSignal);
  }
});

// When you want to cancel the operation.
cancellationSignal.cancel();

您也可以取消 ART 服務啟動的背景 dexopt。

getArtManagerLocal().cancelBackgroundDexoptJob();

取得 dexopt 結果

如果作業是由 dexoptPackage 呼叫啟動,您可以從回傳值取得結果。

DexoptResult result;
try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
  result = getArtManagerLocal().dexoptPackage(...);
}

// Process the result here.
...

在許多情況下,ART 服務也會自行啟動 dexopt 作業,例如背景 dexopt。如要監聽所有 dexopt 結果 (無論作業是由 dexoptPackage 呼叫或 ART 服務啟動),請使用 ArtManagerLocal#addDexoptDoneCallback

getArtManagerLocal().addDexoptDoneCallback(
    false /* onlyIncludeUpdates */,
    Runnable::run,
    (result) -> {
      // Process the result here.
      ...
    });

第一個引數會決定結果是否只包含更新。如果只想監聽 dexopt 更新的套件,請將這個值設為 true。

第二個引數是回呼的執行工具。如要在執行 dexopt 的同一執行緒上執行回呼,請使用 Runnable::run。如果不想讓回呼封鎖 dexopt,請使用非同步執行工具。

您可以新增多個回呼,ART 服務會依序執行所有回呼。除非移除回電,否則所有回電都會在日後的通話中保持啟用狀態。

如要移除回呼,請在新增回呼時保留回呼的參照,並使用 ArtManagerLocal#removeDexoptDoneCallback

DexoptDoneCallback callback = (result) -> {
  // Process the result here.
  ...
};

getArtManagerLocal().addDexoptDoneCallback(
    false /* onlyIncludeUpdates */, Runnable::run, callback);

// When you want to remove it.
getArtManagerLocal().removeDexoptDoneCallback(callback);

自訂套件清單和 dexopt 參數

ART 服務會在開機和背景 dexopt 期間自行啟動 dexopt 作業。如要自訂這些作業的套件清單或 dexopt 參數,請使用 ArtManagerLocal#setBatchDexoptStartCallback

getArtManagerLocal().setBatchDexoptStartCallback(
    Runnable::run,
    (snapshot, reason, defaultPackages, builder, cancellationSignal) -> {
      switch (reason) {
        case ReasonMapping.REASON_BG_DEXOPT:
          var myPackages = new ArrayList<String>(defaultPackages);
          myPackages.add(...);
          myPackages.remove(...);
          myPackages.sort(...);
          builder.setPackages(myPackages);
          break;
        default:
          // Ignore unknown reasons.
      }
    });

您可以新增或移除套件清單中的項目、排序清單,甚至使用完全不同的清單。

回呼必須忽略不明原因,因為日後可能會新增更多原因。

你最多可以設定一個 BatchDexoptStartCallback。除非清除回呼,否則回呼會保持啟用狀態,適用於日後的所有通話。

如要清除回呼,請使用 ArtManagerLocal#clearBatchDexoptStartCallback

getArtManagerLocal().clearBatchDexoptStartCallback();

自訂背景 dexopt 作業的參數

根據預設,背景 dexopt 工作會在裝置閒置且充電時每天執行一次。如要變更這項設定,請使用ArtManagerLocal#setScheduleBackgroundDexoptJobCallback

getArtManagerLocal().setScheduleBackgroundDexoptJobCallback(
    Runnable::run,
    builder -> {
      builder.setPeriodic(TimeUnit.DAYS.toMillis(2));
    });

你最多可以設定一個 ScheduleBackgroundDexoptJobCallback。除非清除回呼,否則日後所有通話都會繼續使用這項功能。

如要清除回呼,請使用 ArtManagerLocal#clearScheduleBackgroundDexoptJobCallback

getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();

暫時停用 dexopt

ART 服務啟動的任何 dexopt 作業都會觸發 BatchDexoptStartCallback。您可以持續取消作業,有效停用 dexopt。

如果取消的作業是背景 dexopt,系統會採用預設的重試政策 (30 秒,指數型,上限為 5 小時)。

// Good example.

var shouldDisableDexopt = new AtomicBoolean(false);

getArtManagerLocal().setBatchDexoptStartCallback(
    Runnable::run,
    (snapshot, reason, defaultPackages, builder, cancellationSignal) -> {
      if (shouldDisableDexopt.get()) {
        cancellationSignal.cancel();
      }
    });

// Disable dexopt.
shouldDisableDexopt.set(true);
getArtManagerLocal().cancelBackgroundDexoptJob();

// Re-enable dexopt.
shouldDisableDexopt.set(false);

最多只能有一個 BatchDexoptStartCallback。如果您也想使用 BatchDexoptStartCallback 自訂套件清單或 dexopt 參數,則必須將程式碼合併為一個回呼。

// Bad example.

// Disable dexopt.
getArtManagerLocal().unscheduleBackgroundDexoptJob();

// Re-enable dexopt.
getArtManagerLocal().scheduleBackgroundDexoptJob();

應用程式安裝時執行的 dexopt 作業不會由 ART 服務啟動。而是由套件管理工具透過 dexoptPackage 呼叫啟動。因此,系統不會觸發 BatchDexoptStartCallback。如要在安裝應用程式時停用 dexopt,請禁止套件管理員呼叫 dexoptPackage

為特定套件覆寫編譯器篩選器 (Android 15 以上版本)

您可以透過 setAdjustCompilerFilterCallback 註冊回呼,覆寫特定套件的編譯器篩選器。無論 dexopt 是由 ART 服務在啟動和背景 dexopt 期間啟動,還是由 dexoptPackage API 呼叫啟動,只要套件即將進行 dexopt,就會呼叫回呼。

如果套件不需要調整,回呼必須傳回 originalCompilerFilter

getArtManagerLocal().setAdjustCompilerFilterCallback(
    Runnable::run,
    (packageName, originalCompilerFilter, reason) -> {
      if (isVeryImportantPackage(packageName)) {
        return "speed-profile";
      }
      return originalCompilerFilter;
    });

你只能設定一個 AdjustCompilerFilterCallback。如要使用 AdjustCompilerFilterCallback 覆寫多個套件的編譯器篩選器,請將程式碼合併為一個回呼。除非清除回呼,否則日後所有通話都會繼續使用。

如要清除回呼,請使用 ArtManagerLocal#clearAdjustCompilerFilterCallback

getArtManagerLocal().clearAdjustCompilerFilterCallback();

其他自訂項目

ART 服務也支援其他自訂項目。

設定背景 dexopt 的熱能閾值

背景 dexopt 作業的熱控制是由 Job Scheduler 執行。 溫度達到 THERMAL_STATUS_MODERATE 時,工作會立即取消。THERMAL_STATUS_MODERATE 的門檻可調整。

判斷背景 dexopt 是否正在執行

背景 dexopt 工作是由 Job Scheduler 管理,工作 ID 為 27873780。如要判斷工作是否正在執行,請使用 Job Scheduler API。

// Good example.

var jobScheduler =
    Objects.requireNonNull(mContext.getSystemService(JobScheduler.class));
int reason = jobScheduler.getPendingJobReason(27873780);

if (reason == PENDING_JOB_REASON_EXECUTING) {
  // Do something when the job is running.
  ...
}
// Bad example.

var backgroundDexoptRunning = new AtomicBoolean(false);

getArtManagerLocal().setBatchDexoptStartCallback(
    Runnable::run,
    (snapshot, reason, defaultPackages, builder, cancellationSignal) -> {
      if (reason.equals(ReasonMapping.REASON_BG_DEXOPT)) {
        backgroundDexoptRunning.set(true);
      }
    });

getArtManagerLocal().addDexoptDoneCallback(
    false /* onlyIncludeUpdates */,
    Runnable::run,
    (result) -> {
      if (result.getReason().equals(ReasonMapping.REASON_BG_DEXOPT)) {
        backgroundDexoptRunning.set(false);
      }
    });

if (backgroundDexoptRunning.get()) {
  // Do something when the job is running.
  ...
}

提供 dexopt 設定檔

如要使用設定檔引導 dexopt,請將 .prof 檔案或 .dm 檔案放在 APK 旁邊。

.prof 檔案必須是二進位格式的設定檔檔案,且檔案名稱必須是 APK 的檔案名稱加上 .prof。例如:

base.apk.prof

.dm 檔案的名稱必須與 APK 檔案名稱相同,但副檔名要改為 .dm。例如:

base.dm

如要確認設定檔是否用於 dexopt,請執行 speed-profile 的 dexopt,並檢查結果。

pm art clear-app-profiles <package-name>
pm compile -m speed-profile -f -v <package-name>

第一行會清除執行階段產生的所有設定檔 (即 /data/misc/profiles 中的設定檔,如有),確保 APK 旁邊的設定檔是 ART 服務唯一可能使用的設定檔。第二行會使用 speed-profile 執行 dexopt,並傳遞 -v 來列印詳細結果。

如果正在使用該設定檔,結果中會顯示 actualCompilerFilter=speed-profile。否則會看到 actualCompilerFilter=verify。例如:

DexContainerFileDexoptResult{dexContainerFile=/data/app/~~QR0fTV0UbDbIP1Su7XzyPg==/com.google.android.gms-LvusF2uARKOtBbcaPHdUtQ==/base.apk, primaryAbi=true, abi=x86_64, actualCompilerFilter=speed-profile, status=PERFORMED, dex2oatWallTimeMillis=4549, dex2oatCpuTimeMillis=14550, sizeBytes=3715344, sizeBeforeBytes=3715344}

ART 服務通常不會使用設定檔,原因包括:

  • 設定檔的檔案名稱有誤,或設定檔不在 APK 旁邊。
  • 設定檔格式有誤。
  • 設定檔與 APK 不符。(設定檔中的總和檢查碼與 APK 中 .dex 檔案的總和檢查碼不符)。