ART サービスの設定

開始する前に、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#createPackageContextCONTEXT_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 呼び出しの回数を制御します。つまり、同時スレッドの最大数は、2 つのシステム プロパティの積となります。
  • dalvik.vm.*dex2oat-cpu-set とタスク プロファイルは、同時スレッドの最大数(上記で説明)に関係なく、常に CPU コアの使用率を制限します。

dex2oat の単独呼び出しでは、dalvik.vm.*dex2oat-threads に関係なく CPU コアを十分に使用できない可能性があります。そのため、dex2oat 呼び出しの回数(pm.dexopt.<reason>.concurrency)を増やすことによって、CPU コアの使用率が上がり、dexopt の全体的な進行がスピードアップします。これは起動時に特に役に立ちます。

しかし、dex2oat 呼び出しの回数が多すぎるとデバイスのメモリ不足につながることがあります。その場合は dalvik.vm.dex2oat-swaptrue に設定してスワップ ファイルの使用を許可することで緩和される可能性があります。また、呼び出しが多すぎると不必要なコンテキストの切り替えを招く場合もあります。そのため、この値はプロダクトごとに慎重に調整する必要があります。

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 ジョブがまだスケジュールされていなければスケジュールできないようにします。次の行は、すでにスケジュールされている場合はそのスケジュールを取り消し、ジョブが実行中の場合は直ちにキャンセルします。

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 を呼び出してこれを取得します。PackageManagerLocalLocalManagerRegistry にあるシングルトンで、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 ファイルのチェックサムと一致しない)。