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 (預設值:速度)

這是其他應用程式使用的應用程式備用編譯器篩選器。

原則上,ART Service 會盡可能對所有應用程式執行設定檔引導編譯 (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 工作執行:

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 是否在啟動和背景 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 工作的熱控作業由工作排程器執行。溫度達到 THERMAL_STATUS_MODERATE 時,系統會立即取消工作。THERMAL_STATUS_MODERATE 的門檻可調整。

判斷 dexopt 是否正在執行 dexopt 背景

背景 dexopt 工作由工作排程器管理,其工作 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,請在 APK 旁邊加入 .prof 檔案或 .dm 檔案。

.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 檔案的總和檢查碼不符)。