在开始之前,请参阅 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#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 调用次数。也就是说,并发线程数上限是这两个系统属性的乘积。- 无论并发线程数上限(如上所述)是多少,
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_percentage
和 sys_storage_threshold_max_bytes
配置,默认值:500MB)加上 500MB。
如果您通过 ArtManagerLocal#setBatchDexoptStartCallback
自定义软件包列表,则 BatchDexoptStartCallback
为 bg-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
文件的校验和不匹配。)