ART Service 配置

在开始之前,请参阅 ART Service 的高级概览。

从 Android 14 开始,应用的设备端 AOT 编译(又名 dexopt)由 ART Service 处理。ART Service 是 ART 模块的一部分,您可以通过系统属性和 API 对其进行自定义。

系统属性

ART Service 支持所有相关的 dex2oat 选项

此外,ART Service 还支持以下系统属性

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 Service 会在可能的情况下对所有应用执行 Profile-Guided Compilation(speed-profile),通常在后台 dexopt 期间。但是,有些应用会被其他应用使用(通过 <uses-library> 或使用 Context#createPackageContextCONTEXT_INCLUDE_CODE 动态加载)。由于隐私原因,此类应用无法使用本地配置文件。

对于此类应用,如果请求 Profile-Guided Compilation,ART Service 会先尝试使用云配置文件。如果云配置文件不存在,ART Service 会回退到使用 pm.dexopt.shared 指定的编译器过滤器。

如果请求的编译不是 Profile-Guided Compilation,则此属性无效。

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 Service 将仅对在过去给定天数内使用过的应用执行 dexopt。

此外,如果存储空间即将不足,在后台 dexopt 期间,ART Service 会降低过去给定天数内未使用过的应用的编译器过滤器,以释放空间。此操作的编译器原因是 inactive,编译器过滤器由 pm.dexopt.inactive 确定。触发此功能的空间阈值是 Storage Manager 的低空间阈值(可通过全局设置 sys_storage_threshold_percentagesys_storage_threshold_max_bytes 配置,默认值:500MB)加上 500MB。

如果您通过 ArtManagerLocal#setBatchDexoptStartCallback 自定义软件包列表,则 BatchDexoptStartCallbackbg-dexopt 提供的列表中的软件包永远不会被降级。

pm.dexopt.disable_bg_dexopt(默认值:false)

此选项仅用于测试。它可以阻止 ART Service 调度后台 dexopt 作业。

如果后台 dexopt 作业已经调度但尚未运行,则此选项无效。也就是说,该作业仍将运行。

防止后台 dexopt 作业运行的推荐命令序列是

setprop pm.dexopt.disable_bg_dexopt true
pm bg-dexopt-job --disable

第一行防止调度尚未调度的后台 dexopt 作业。第二行取消调度已调度的后台 dexopt 作业,如果后台 dexopt 作业正在运行,则立即取消它。

ART Service API

ART Service 公开了用于自定义的 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 Service 启动的后台 dexopt。

getArtManagerLocal().cancelBackgroundDexoptJob();

获取 dexopt 结果

如果操作是由 dexoptPackage 调用启动的,您可以从返回值中获取结果。

DexoptResult result;
try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
  result = getArtManagerLocal().dexoptPackage(...);
}

// Process the result here.
...

ART Service 还在许多场景中自行启动 dexopt 操作,例如后台 dexopt。要监听所有 dexopt 结果,无论操作是由 dexoptPackage 调用还是由 ART Service 启动的,请使用 ArtManagerLocal#addDexoptDoneCallback

getArtManagerLocal().addDexoptDoneCallback(
    false /* onlyIncludeUpdates */,
    Runnable::run,
    (result) -> {
      // Process the result here.
      ...
    });

第一个参数确定是否仅在结果中包含更新。如果您只想监听由 dexopt 更新的软件包,请将其设置为 true。

第二个参数是回调的执行程序。要在执行 dexopt 的同一线程上执行回调,请使用 Runnable::run。如果您不希望回调阻止 dexopt,请使用异步执行程序。

您可以添加多个回调,ART Service 将按顺序执行所有回调。所有回调将在所有未来调用中保持活动状态,除非您将其删除。

如果要删除回调,请在添加回调时保留回调的引用,并使用 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 Service 在启动和后台 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 Service 启动的任何 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 Service 启动的。相反,它是由软件包管理器通过 dexoptPackage 调用启动的。因此,它不会触发 BatchDexoptStartCallback。要禁用应用安装时的 dexopt,请阻止软件包管理器调用 dexoptPackage

覆盖某些软件包的编译器过滤器 (Android 15+)

您可以通过注册 setAdjustCompilerFilterCallback 的回调来覆盖某些软件包的编译器过滤器。每当要对软件包执行 dexopt 时,都会调用该回调,无论 dexopt 是由 ART Service 在启动和后台 dexopt 期间启动的,还是由 dexoptPackage API 调用启动的。

如果软件包不需要调整,回调必须返回 originalCompilerFilter

getArtManagerLocal().setAdjustCompilerFilterCallback(
    Runnable::run,
    (packageName, originalCompilerFilter, reason) -> {
      if (isVeryImportantPackage(packageName)) {
        return "speed-profile";
      }
      return originalCompilerFilter;
    });

您只能设置一个 AdjustCompilerFilterCallback。如果您想使用 AdjustCompilerFilterCallback 来覆盖多个软件包的编译器过滤器,则必须将代码合并到一个回调中。回调将在所有未来调用中保持活动状态,除非您将其清除。

如果要清除回调,请使用 ArtManagerLocal#clearAdjustCompilerFilterCallback

getArtManagerLocal().clearAdjustCompilerFilterCallback();

其他自定义

ART Service 还支持其他一些自定义。

设置后台 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 Service 可能使用的唯一配置文件。第二行使用 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 Service 不使用配置文件的典型原因包括以下几种

  • 配置文件的文件名错误或未放在 APK 旁边。
  • 配置文件的格式错误。
  • 配置文件与 APK 不匹配。(配置文件中的校验和与 APK 中 .dex 文件的校验和不匹配。)