開始前,請先參閱 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-boot
、boot-after-ota
、boot-after-mainline-update
和 bg-dexopt
) 而呼叫 dex2oat 的次數。
請注意,這個選項的效果會與 dex2oat 資源用量選項 (dalvik.vm.*dex2oat-threads
、dalvik.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_percentage
和 sys_storage_threshold_max_bytes
設定,預設為 500MB) 加上 500MB。
如果您透過 ArtManagerLocal#setBatchDexoptStartCallback
自訂套件清單,BatchDexoptStartCallback
為 bg-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 來源、未發布的開發來源)。
ArtManagerLocal
是 LocalManagerRegistry
持有的單例。輔助函式 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
檔案的總和檢查碼不符)。