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 服务会尽可能对所有应用执行配置文件引导的编译 (speed-profile)(通常在后台 dexopt 期间执行)。不过,也有一些应用被其他应用使用(通过 <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 调用次数。也就是说,并发线程数上限是这两个系统属性的乘积。
  • 无论并发线程数上限(如上所述)是多少,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(默认值:not set)

如果设置了此选项,ART 服务只会对过去给定天数内使用过的应用进行 dexopt 处理。

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

如果您通过 ArtManagerLocal#setBatchDexoptStartCallback 自定义软件包列表,则 BatchDexoptStartCallbackbg-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 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 [AOSP 实验版] 及更高版本)的编译过滤器

您可通过 setAdjustCompilerFilterCallback 注册回调,以替换某些软件包的编译过滤器。每当要对软件包执行 dexopt 处理时,系统都会调用该回调,无论 dexopt 是由 ART 服务在启动期间和后台 dexopt 中发起,还是由 dexoptPackage API 调用发起。

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

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

您只能设置 1 个 AdjustCompilerFilterCallback。如果要使用 AdjustCompilerFilterCallback 替换多个软件包的编译过滤器,您必须将代码组合到 1 个回调中。除非您清除该回调,否则它对所有未来调用都会保持活动状态。

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

getArtManagerLocal().clearAdjustCompilerFilterCallback();

其他自定义设置

ART 服务还支持一些其他自定义设置。

设置后台 dexopt 的热阈值

后台 dexopt 作业的热控制由作业调度程序执行。当温度达到 THERMAL_STATUS_MODERATE 时,作业会立即取消。 阈值 THERMAL_STATUS_MODERATE 可调。

确定后台 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,请将 .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 文件的校验和不匹配。)