Skip to content

ArtManagerService

追踪 ArtManagerService 的权限、路径校验、profile 快照回调与编译统计边界。

基于android-17.0.0_r1
AndroidPMSARTProfiles

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,符号:类声明与构造函数

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_serverprofile FD 或错误码
ArtManagerInternal LocalServicesystem_server 内部调用PackageOptimizationInfo

服务本身不保存 profile 文件,也不启动独立 profile worker。mHandler 只负责把结果回调投递到 BackgroundThread;profile 的聚合和临时文件由 ART Service 的 ArtManagerLocal/artd 拥有。

2. 客户端契约 ​

源码文件:frameworks/base/core/java/android/content/pm/dex/IArtManager.aidl,符号:接口声明

java
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

java
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

java
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

java
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

java
@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

java
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。

java
    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

java
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 快照。

java
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

java
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

java
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

java
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 或 IllegalStateExceptionSNAPSHOT_FAILED_INTERNAL_ERROR
FD 异步投递前失效postSuccess valid 检查失败SNAPSHOT_FAILED_INTERNAL_ERROR
调用方无权限Binder 入口提前返回SNAPSHOT_FAILED_INTERNAL_ERROR

读源码时可以用以下问题复核主线:

  1. 为什么 user 构建的 shell 不能 snapshot boot image,但可以 snapshot debuggable app?
  2. 为什么 PackageInfo 查询通过后仍可能收到 package-not-found?
  3. 为什么 profile 生成成功不等于 callback 立即执行?
  4. install-dm、cloud 和 bg-dexopt 在统计映射中为何必须分开?

这些问题分别对应权限边界、双 snapshot 竞态、FD 生命周期和统计语义。真正要定位 profile 内容问题时,继续进入 ArtManagerLocal.snapshotAppProfile 与 DexMetadataHelper;ArtManagerService 只负责门面、策略检查和结果交付。