開始する前に、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 サービスはあらゆるアプリに対して、通常はバックグラウンド dexopt を実行中に可能な範囲でプロファイル ベースのコンパイル(speed-profile
)を実行します。ただし、アプリが他のアプリによって使用中の場合(<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 呼び出しの回数を制御します。つまり、同時スレッドの最大数は、2 つのシステム プロパティの積となります。dalvik.vm.*dex2oat-cpu-set
とタスク プロファイルは、同時スレッドの最大数(上記で説明)に関係なく、常に CPU コアの使用率を制限します。
dex2oat の単独呼び出しでは、dalvik.vm.*dex2oat-threads
に関係なく 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 ジョブがまだスケジュールされていなければスケジュールできないようにします。次の行は、すでにスケジュールされている場合はそのスケジュールを取り消し、ジョブが実行中の場合は直ちにキャンセルします。
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 に設定します。
2 つ目の引数はコールバックのエグゼキュータです。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.
}
});
パッケージ リストへのアイテムの追加、パッケージ リストからのアイテムの削除、リストの並べ替え、まったく異なるリストの使用も可能です。
コールバックは不明な理由を無視する必要があります。これはより多くの理由が今後追加される可能性があるためです。
最大 1 つの BatchDexoptStartCallback
を設定できます。コールバックは消去しない限り、今後のすべての呼び出しで有効なままとなります。
コールバックを消去するには ArtManagerLocal#clearBatchDexoptStartCallback
を使用します。
getArtManagerLocal().clearBatchDexoptStartCallback();
バックグラウンド dexopt ジョブのパラメータをカスタマイズする
デフォルトでは、バックグラウンド dexopt ジョブはデバイスがアイドル状態で充電中のときに 1 日 1 回実行されます、これを変更するには ArtManagerLocal#setScheduleBackgroundDexoptJobCallback
を使用します。
getArtManagerLocal().setScheduleBackgroundDexoptJobCallback(
Runnable::run,
builder -> {
builder.setPeriodic(TimeUnit.DAYS.toMillis(2));
});
設定できる ScheduleBackgroundDexoptJobCallback
は 1 つだけです。コールバックは消去しない限り、今後のすべての呼び出しで有効なままとなります。
コールバックを消去するには 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
は 1 つだけです。BatchDexoptStartCallback
を使用してパッケージ リストまたは dexopt パラメータのカスタマイズもする場合は、コードを 1 つのコールバックに統合する必要があります。
// 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
は 1 つだけです。AdjustCompilerFilterCallback
を使用して複数のパッケージでコンパイラ フィルタをオーバーライドするには、コードを結合して 1 つのコールバックにする必要があります。コールバックは消去しない限り、今後のすべての呼び出しで有効なままとなります。
コールバックを消去するには 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 で使用されていることを確認するには、dexopt を speed-profile
で実行し、結果を確認します。
pm art clear-app-profiles <package-name>
pm compile -m speed-profile -f -v <package-name>
1 行目はランタイムによって生成されたプロファイル(/data/misc/profiles
内のもの)がある場合にそれをすべて消去して、APK の次にあるプロファイルが ART サービスが使用できる唯一のプロファイルとなるようにします。2 行目は 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
ファイルのチェックサムと一致しない)。