ArtManagerService
本文面向已经读过 ART profiles、后台 dexopt 和 DexMetadataHelper 的读者,专门回答一个调用链问题:ArtManager 发起的运行时 profile 快照,如何经过 ArtManagerService 的权限和路径判断,交给 ArtManagerLocal,再以一个可读的文件描述符异步返回。
先澄清版本边界。Android 17 的 IArtManager AIDL 只声明 snapshotRuntimeProfile 和 isRuntimeProfilingEnabled;旧资料中的 dumpProfiles 不是当前这条 Binder 接口。命令行的 profile dump 由 ART Service shell 命令处理,不能把旧的 ArtManagerService.dumpProfiles() 当作 Android 17 的源码入口。
1. 服务位置
源码文件:frameworks/base/services/core/java/com/android/server/pm/dex/ArtManagerService.java,符号:类声明与构造函数
public class ArtManagerService
extends android.content.pm.dex.IArtManager.Stub {
private final Context mContext;
private IPackageManager mPackageManager;
private final Handler mHandler;
public ArtManagerService(Context context) {
mContext = context;
mHandler = new Handler(
BackgroundThread.getHandler().getLooper());
LocalServices.addService(
ArtManagerInternal.class,
new ArtManagerInternalImpl());
}
}这个对象同时承担两个不同入口:
| 入口 | 对象边界 | 结果 |
|---|---|---|
IArtManager Binder | 应用/调试 shell 到 system_server | profile FD 或错误码 |
ArtManagerInternal LocalService | system_server 内部调用 | PackageOptimizationInfo |
服务本身不保存 profile 文件,也不启动独立 profile worker。mHandler 只负责把结果回调投递到 BackgroundThread;profile 的聚合和临时文件由 ART Service 的 ArtManagerLocal/artd 拥有。
2. 客户端契约
源码文件:frameworks/base/core/java/android/content/pm/dex/IArtManager.aidl,符号:接口声明
interface IArtManager {
void snapshotRuntimeProfile(int profileType,
in String packageName, in String codePath,
in ISnapshotRuntimeProfileCallback callback,
String callingPackage);
boolean isRuntimeProfilingEnabled(
int profileType, String callingPackage);
}应用侧通常不直接操作 Binder,而是从 PackageManager.getArtManager() 得到 ArtManager。ArtManager 将 Java 回调包装为 AIDL delegate,并把调用方包名传给服务用于 AppOps 检查。
源码文件:frameworks/base/core/java/android/content/pm/dex/ArtManager.java,符号:snapshotRuntimeProfile
public void snapshotRuntimeProfile(
@ProfileType int profileType,
@Nullable String packageName,
@Nullable String codePath,
@NonNull @CallbackExecutor Executor executor,
@NonNull SnapshotRuntimeProfileCallback callback) {
SnapshotRuntimeProfileCallbackDelegate delegate =
new SnapshotRuntimeProfileCallbackDelegate(
callback, executor);
try {
mArtManager.snapshotRuntimeProfile(
profileType, packageName, codePath, delegate,
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}客户端的 Executor 不会改变 system_server 内部的 profile 生成线程;它只决定 AIDL delegate 收到结果后,在调用方进程使用哪条线程执行用户回调。ParcelFileDescriptor 的读取责任也在客户端,服务端只负责在回调完成后关闭自己的引用。
3. 权限分层
源码文件:frameworks/base/services/core/java/com/android/server/pm/dex/ArtManagerService.java,符号:checkAndroidPermissions
private boolean checkAndroidPermissions(
int callingUid, String callingPackage) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_RUNTIME_PROFILES,
TAG);
switch (mContext.getSystemService(AppOpsManager.class)
.noteOp(AppOpsManager.OP_GET_USAGE_STATS,
callingUid, callingPackage)) {
case AppOpsManager.MODE_ALLOWED:
return true;
case AppOpsManager.MODE_DEFAULT:
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.PACKAGE_USAGE_STATS,
TAG);
return true;
default:
return false;
}
}标准调用必须同时满足 READ_RUNTIME_PROFILES 和 usage stats 条件:先检查 signature 级 profile 权限,再通过 AppOps 判断 OP_GET_USAGE_STATS;AppOps 返回 default 时,再要求 PACKAGE_USAGE_STATS。AppOps 的 denied 结果返回 false,而 READ_RUNTIME_PROFILES 缺失会直接抛 SecurityException。
源码文件:frameworks/base/services/core/java/com/android/server/pm/dex/ArtManagerService.java,符号:checkShellPermissions
private boolean checkShellPermissions(
@ProfileType int profileType,
String packageName, int callingUid) {
if (callingUid != Process.SHELL_UID) {
return false;
}
if (RoSystemProperties.DEBUGGABLE) {
return true;
}
if (profileType == ArtManager.PROFILE_BOOT_IMAGE) {
return false;
}
PackageInfo info = null;
try {
info = getPackageManager().getPackageInfo(
packageName, 0, 0);
} catch (RemoteException ignored) {
}
return info != null
&& (info.applicationInfo.flags
& ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}shell 不是无条件绕过权限:debuggable 构建允许 shell 快照任意应用;user 构建禁止 shell 快照 boot image,只允许对 debuggable 应用取 app profile。root UID 不走这个分支,而是进入普通 Android 权限检查。
4. Binder 入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/dex/ArtManagerService.java,符号:snapshotRuntimeProfile
@Override
public void snapshotRuntimeProfile(
@ProfileType int profileType,
@Nullable String packageName,
@Nullable String codePath,
@NonNull ISnapshotRuntimeProfileCallback callback,
String callingPackage) {
int callingUid = Binder.getCallingUid();
if (!checkShellPermissions(profileType, packageName, callingUid)
&& !checkAndroidPermissions(
callingUid, callingPackage)) {
try {
callback.onError(
ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
} catch (RemoteException ignored) {
}
return;
}
Objects.requireNonNull(callback);
boolean bootImageProfile =
profileType == ArtManager.PROFILE_BOOT_IMAGE;
if (!bootImageProfile) {
Preconditions.checkStringNotEmpty(codePath);
Preconditions.checkStringNotEmpty(packageName);
}
if (!isRuntimeProfilingEnabled(
profileType, callingPackage)) {
throw new IllegalStateException(
"Runtime profiling is not enabled for "
+ profileType);
}
if (bootImageProfile) {
snapshotBootImageProfile(callback);
} else {
snapshotAppProfile(packageName, codePath, callback);
}
}入口的顺序很重要:权限失败先回调错误并返回;只有权限通过后才检查 callback 和 app 参数;profile 类型是否启用又是独立检查。未知 profileType 不会被当成 app profile,isRuntimeProfilingEnabled 的 switch 会抛出 IllegalArgumentException。
5. App profile 路径
源码文件:frameworks/base/services/core/java/com/android/server/pm/dex/ArtManagerService.java,符号:snapshotAppProfile
private void snapshotAppProfile(
String packageName, String codePath,
ISnapshotRuntimeProfileCallback callback) {
PackageInfo info = null;
try {
info = getPackageManager().getPackageInfo(
packageName, 0, 0);
} catch (RemoteException ignored) {
}
if (info == null) {
postError(callback, packageName,
ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
return;
}
boolean pathFound = info.applicationInfo
.getBaseCodePath().equals(codePath);
String splitName = null;
String[] splitCodePaths = info.applicationInfo
.getSplitCodePaths();
if (!pathFound && splitCodePaths != null) {
for (int i = splitCodePaths.length - 1; i >= 0; i--) {
if (splitCodePaths[i].equals(codePath)) {
pathFound = true;
splitName = info.applicationInfo.splitNames[i];
break;
}
}
}
if (!pathFound) {
postError(callback, packageName,
ArtManager.SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND);
return;
}服务先通过 PMS Binder 查询 user 0 的 PackageInfo,只为了取得 base/split code path;它不把 user 0 的 installed 状态当作 profile 读取权限。base APK 对应 splitName=null,split APK 则把真实 split name 传给 ART。
ParcelFileDescriptor fd;
try (PackageManagerLocal.FilteredSnapshot snapshot =
PackageManagerServiceUtils.getPackageManagerLocal()
.withFilteredSnapshot()) {
fd = DexOptHelper.getArtManagerLocal()
.snapshotAppProfile(
snapshot, packageName, splitName);
} catch (IllegalArgumentException e) {
postError(callback, packageName,
ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
return;
} catch (IllegalStateException
| ArtManagerLocal.SnapshotProfileException e) {
postError(callback, packageName,
ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
return;
}
postSuccess(packageName, fd, callback);
}这里又创建了一个 PackageManagerLocal.FilteredSnapshot。PMS 的 PackageInfo 查询和 ART 快照不是同一个 snapshot,所以中间存在竞态:包或 split 可能在两次读取之间被删除。源码把 ART 层的 IllegalArgumentException 解释为 package-not-found,并在注释中承认这是 race。
6. Boot image
源码文件:frameworks/base/services/core/java/com/android/server/pm/dex/ArtManagerService.java,符号:isRuntimeProfilingEnabled、snapshotBootImageProfile
case ArtManager.PROFILE_APPS:
return true;
case ArtManager.PROFILE_BOOT_IMAGE:
boolean profileBootClassPath = SystemProperties.getBoolean(
"persist.device_config.runtime_native_boot.profilebootclasspath",
SystemProperties.getBoolean(
"dalvik.vm.profilebootclasspath", false));
return (Build.IS_USERDEBUG || Build.IS_ENG)
&& profileBootClassPath;
default:
throw new IllegalArgumentException(
"Invalid profile type:" + profileType);app profile 始终报告 enabled;boot image profile 需要 userdebug/eng 构建,并且 device config 属性优先、系统属性作为 fallback。生产 user 构建即使调用参数正确,也不会进入 boot image 快照。
private void snapshotBootImageProfile(
ISnapshotRuntimeProfileCallback callback) {
ParcelFileDescriptor fd;
try (PackageManagerLocal.FilteredSnapshot snapshot =
PackageManagerServiceUtils.getPackageManagerLocal()
.withFilteredSnapshot()) {
fd = DexOptHelper.getArtManagerLocal()
.snapshotBootImageProfile(snapshot);
} catch (IllegalStateException
| ArtManagerLocal.SnapshotProfileException e) {
postError(callback, PLATFORM_PACKAGE_NAME,
ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
return;
}
postSuccess(PLATFORM_PACKAGE_NAME, fd, callback);
}Boot image 路径不查询某个应用的 code path,调用 ART 的聚合接口并把平台包名用于日志和错误上下文。实际参与聚合的 bootclasspath、system server 和应用 profile 列表属于 ArtManagerLocal,不是 Binder 门面。
7. 回调与 FD 所有权
源码文件:frameworks/base/services/core/java/com/android/server/pm/dex/ArtManagerService.java,符号:postError、postSuccess
private void postError(ISnapshotRuntimeProfileCallback callback,
String packageName, int errCode) {
mHandler.post(() -> {
try {
callback.onError(errCode);
} catch (RemoteException | RuntimeException e) {
Slog.w(TAG,
"Failed to callback after profile snapshot for "
+ packageName, e);
}
});
}
private void postSuccess(String packageName,
ParcelFileDescriptor fd,
ISnapshotRuntimeProfileCallback callback) {
mHandler.post(() -> {
try {
if (fd.getFileDescriptor().valid()) {
callback.onSuccess(fd);
} else {
Slog.wtf(TAG,
"The snapshot FD became invalid before posting");
callback.onError(
ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
}
} catch (RemoteException | RuntimeException e) {
Slog.w(TAG,
"Failed to call onSuccess after profile snapshot",
e);
} finally {
IoUtils.closeQuietly(fd);
}
});
}快照生成可能已经在 Binder 线程返回 FD,但真正的回调延迟到 BackgroundThread。投递前再次检查 FileDescriptor.valid(),针对的是历史上 FD 在异步等待期间失效的问题;无论回调成功、失败还是调用方断开,服务 finally 都关闭自己的 FD 引用。客户端收到 onSuccess 后必须在自己的回调中读取并关闭对应 descriptor 引用。
8. 内部优化信息
公开 Binder 快照之外,构造函数还注册 ArtManagerInternal。它被 system_server 内部的 metrics 使用,不是 profile snapshot 的替代入口。
源码文件:frameworks/base/services/core/java/com/android/server/pm/dex/ArtManagerService.java,符号:ArtManagerInternalImpl.getPackageOptimizationInfo
public PackageOptimizationInfo getPackageOptimizationInfo(
ApplicationInfo info, String abi, String activityName) {
if (info.packageName.equals(PLATFORM_PACKAGE_NAME)) {
return PackageOptimizationInfo.createWithNoInfo();
}
String compilationReason;
String compilationFilter;
try {
String isa = VMRuntime.getInstructionSet(abi);
DexFile.OptimizationInfo optInfo =
DexFile.getDexFileOptimizationInfo(
info.getBaseCodePath(), isa);
compilationFilter = optInfo.getStatus();
compilationReason = optInfo.getReason();
} catch (FileNotFoundException | IllegalArgumentException e) {
compilationFilter = "error";
compilationReason = "error";
}
return new PackageOptimizationInfo(
getCompilationFilterTronValue(compilationFilter),
getCompilationReasonTronValue(compilationReason));
}这个内部接口读取的是 dex 文件优化状态,不是读取 profile 文件。平台包返回 no-info;无效 ABI 或找不到 code path 映射为 error;正常状态再转换为固定的 TRON 整数。它的消费者是启动/Activity metrics,不应被误称为 snapshotRuntimeProfile 的另一条实现。
9. TRON reason 映射
源码文件:frameworks/base/services/core/java/com/android/server/pm/dex/ArtManagerService.java,符号:getCompilationReasonTronValue
case "install":
return TRON_COMPILATION_REASON_INSTALL;
case "install-dm":
return TRON_COMPILATION_REASON_INSTALL_WITH_DM;
case "bg-dexopt":
return TRON_COMPILATION_REASON_BG_DEXOPT;
case "cloud":
return TRON_COMPILATION_REASON_CLOUD;
default:
return TRON_COMPILATION_REASON_UNKNOWN;这里的 install-dm 与 cloud 是不同 reason:前者表示安装时带 Dex Metadata 标记,后者是 ART 状态中的云端 reason。映射只用于统计,不会反过来改变 compiler filter 或 profile 输入。固定整数值不能随意重排,因为现有统计消费者依赖这些编号。
10. 失败路径与验证
| 现象 | 服务分支 | 回调结果 |
|---|---|---|
| 包名不存在 | snapshotAppProfile 首次 PMS 查询为空 | SNAPSHOT_FAILED_PACKAGE_NOT_FOUND |
| code path 不属于 base/split | 路径匹配失败 | SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND |
| 包在两次 snapshot 间被删除 | ART IllegalArgumentException | 按 package-not-found 处理 |
| ART merge/I/O/状态错误 | SnapshotProfileException 或 IllegalStateException | SNAPSHOT_FAILED_INTERNAL_ERROR |
| FD 异步投递前失效 | postSuccess valid 检查失败 | SNAPSHOT_FAILED_INTERNAL_ERROR |
| 调用方无权限 | Binder 入口提前返回 | SNAPSHOT_FAILED_INTERNAL_ERROR |
读源码时可以用以下问题复核主线:
- 为什么 user 构建的 shell 不能 snapshot boot image,但可以 snapshot debuggable app?
- 为什么
PackageInfo查询通过后仍可能收到 package-not-found? - 为什么 profile 生成成功不等于 callback 立即执行?
install-dm、cloud和bg-dexopt在统计映射中为何必须分开?
这些问题分别对应权限边界、双 snapshot 竞态、FD 生命周期和统计语义。真正要定位 profile 内容问题时,继续进入 ArtManagerLocal.snapshotAppProfile 与 DexMetadataHelper;ArtManagerService 只负责门面、策略检查和结果交付。
