在开始之前,请参阅 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#createPackageContext
和 CONTEXT_INCLUDE_CODE
动态加载)。由于隐私原因,此类应用无法使用本地配置文件。
对于此类应用,如果请求 Profile-Guided Compilation,ART Service 会先尝试使用云配置文件。如果云配置文件不存在,ART Service 会回退到使用 pm.dexopt.shared
指定的编译器过滤器。
如果请求的编译不是 Profile-Guided Compilation,则此属性无效。
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 Service 将仅对在过去给定天数内使用过的应用执行 dexopt。
此外,如果存储空间即将不足,在后台 dexopt 期间,ART Service 会降低过去给定天数内未使用过的应用的编译器过滤器,以释放空间。此操作的编译器原因是 inactive
,编译器过滤器由 pm.dexopt.inactive
确定。触发此功能的空间阈值是 Storage Manager 的低空间阈值(可通过全局设置 sys_storage_threshold_percentage
和 sys_storage_threshold_max_bytes
配置,默认值:500MB)加上 500MB。
如果您通过 ArtManagerLocal#setBatchDexoptStartCallback
自定义软件包列表,则 BatchDexoptStartCallback
为 bg-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 源代码,未发布的开发源代码)。
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 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
文件的校验和不匹配。)