ART 服務設定

開始前,請先概略瞭解 ART Service

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

系統屬性

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。

此外,如果儲存空間即將用盡,在背景 dexopt 期間,ART 服務會降級最近指定天數內未使用的應用程式編譯器篩選器,以釋出空間。編譯器的原因是 inactive,而編譯器篩選器則由 pm.dexopt.inactive 決定。觸發這項功能的空間容量門檻是儲存空間管理員的低容量門檻 (可透過全域設定 sys_storage_threshold_percentagesys_storage_threshold_max_bytes 設定,預設值為 500 MB) 加上 500 MB。

如果您透過 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 工作已排程,第二行會取消排程,如果該工作正在執行,則會立即取消。

ART Service 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,系統就會呼叫回呼,無論 dexopt 是在啟動和背景 dexopt 期間由 ART Service 啟動,還是由 dexoptPackage API 呼叫所啟動。

如果套件不需要調整,回呼必須傳回 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 工作的熱控作業由工作排程器執行。溫度達到 THERMAL_STATUS_MODERATE 時,系統會立即取消工作。THERMAL_STATUS_MODERATE 的閾值可調整。

判斷是否正在執行背景 dexopt

背景 dexopt 工作由工作排程器管理,其工作 ID 為 27873780。如要判斷工作是否正在執行,請使用工作排程器 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>

第一行會清除執行階段產生的所有設定檔 (如果有),確保 APK 旁邊的設定檔是 ART 服務可能使用的唯一設定檔。/data/misc/profiles第二行會使用 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 檔案總和檢查碼不符)。