開始前,請先概略瞭解 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-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。
此外,如果儲存空間即將用盡,在背景 dexopt 期間,ART 服務會降級最近指定天數內未使用的應用程式編譯器篩選器,以釋出空間。編譯器的原因是 inactive
,而編譯器篩選器則由 pm.dexopt.inactive
決定。觸發這項功能的空間容量門檻是儲存空間管理員的低容量門檻 (可透過全域設定 sys_storage_threshold_percentage
和 sys_storage_threshold_max_bytes
設定,預設值為 500 MB) 加上 500 MB。
如果您透過 ArtManagerLocal#setBatchDexoptStartCallback
自訂套件清單,則 BatchDexoptStartCallback
為 bg-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 來源、未發布的開發來源)。
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 在套件遭到 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
檔案的總和檢查碼不符)。