Skip to content

ART profiles

追踪运行时 profile 的采集、快照、消费与清理,建立 PMS 与 ART Service 的调用边界。

AndroidPMSDexOptART

ART profiles ​

ART profile 不是 Package Manager 自己维护的一张“热门方法表”。在 Android 17 中,PMS 通过 ArtManagerLocal 把包快照、安装场景和清理请求交给 ART Service;运行时写入的 cur/ref profile、profile merge 以及 dexopt 时的读取都由 ART 侧完成。理解这一边界,才能解释为什么安装时可以忽略 profile,却不能把 profile 当成永久的包属性。

1. 先确定 owner ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/DexOptHelper.java

java
public static void initializeArtManagerLocal(
        @NonNull Context systemContext, @NonNull PackageManagerService pm) {
    ArtManagerLocal artManager = new ArtManagerLocal(systemContext);
    artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run,
            pm.getDexOptHelper().new DexoptDoneHandler());
    LocalManagerRegistry.addManager(ArtManagerLocal.class, artManager);
    sArtManagerLocalIsInitialized = true;

    systemContext.registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            context.unregisterReceiver(this);
            artManager.scheduleBackgroundDexoptJob();
        }
    }, new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED));

    StagedApexObserver.registerForStagedApexUpdates(artManager);
}

这里有三个容易混淆的角色:

角色真实职责
PackageManagerService提供包状态、安装/升级/删除时机,以及 shell/Binder 入口
DexOptHelper创建并注册 ArtManagerLocal,把 PMS 事件转换为 ART 请求
ART Service管理 profile 文件、profile snapshot/merge、dexopt 和后台任务

LocalManagerRegistry 说明这是 system_server 内部的本地接口,不是应用进程直接调用的 Binder owner。应用运行时产生 profile 的写入者仍是 ART/runtime;PMS 只在需要时提供包视图和策略输入。

2. 采集结果 ​

PMS 不会轮询每个进程的热门方法。对外的 profile snapshot 走 IPackageManager 的回调契约;shell 命令只是一个同步等待这个回调的客户端。

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java

java
private static class SnapshotRuntimeProfileCallback
        extends ISnapshotRuntimeProfileCallback.Stub {
    private boolean mSuccess = false;
    private int mErrCode = -1;
    private ParcelFileDescriptor mProfileReadFd = null;
    private final CountDownLatch mDoneSignal = new CountDownLatch(1);

    @Override
    public void onSuccess(ParcelFileDescriptor profileReadFd) {
        mSuccess = true;
        try {
            // server side also closes its descriptor, so keep a duplicate locally.
            mProfileReadFd = profileReadFd.dup();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mDoneSignal.countDown();
    }

    @Override
    public void onError(int errCode) {
        mSuccess = false;
        mErrCode = errCode;
        mDoneSignal.countDown();
    }
}

这个回调揭示了 snapshot 的数据边界:结果不是直接返回一段 Java byte[],而是一个 ParcelFileDescriptor。调用者从文件描述符读取 ART 生成的 profile 数据;失败时只得到错误码。dup() 是必要的生命周期处理,因为服务端完成回调后会关闭自己的 descriptor。

同步等待并不代表 ART 操作一定是同步实现。shell 侧的 CountDownLatch 只是把异步回调转换成命令行可用的阻塞结果;超时、descriptor 复制失败和 ART 错误仍然需要分别判断。

3. 交接给 dexopt ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/DexOptHelper.java

java
private DexoptParams getDexoptParamsByInstallRequest(
        InstallRequest installRequest) {
    String compilationReason =
            mInstallScenarioHelper.getCompilationReasonForInstallScenario(
                    installRequest.getInstallScenario());
    var builder = new DexoptParams.Builder(compilationReason);
    if (installRequest.getInstallReason() == INSTALL_REASON_DEVICE_RESTORE
            || installRequest.getInstallReason() == INSTALL_REASON_DEVICE_SETUP) {
        builder.setPriorityClass(ArtFlags.PRIORITY_INTERACTIVE_FAST);
    }
    if (installRequest.getDexoptCompilerFilter() != null) {
        builder.setCompilerFilter(installRequest.getDexoptCompilerFilter());
    } else if (shouldSkipDexopt(installRequest)) {
        builder.setCompilerFilter(DexoptParams.COMPILER_FILTER_NOOP);
    }
    if ((installRequest.getInstallFlags()
            & PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE) != 0) {
        builder.setFlags(ArtFlags.FLAG_IGNORE_PROFILE, ArtFlags.FLAG_IGNORE_PROFILE);
    }
    return builder.build();
}

这里的 FLAG_IGNORE_PROFILE 是一次 dexopt 请求的输入,不是删除 profile 文件的命令。它告诉 ART 在本次编译决策中不要使用 profile;之后的运行时采集和后台 dexopt 仍可重新建立 profile 驱动的编译结果。

源码文件:frameworks/base/services/core/java/com/android/server/pm/DexOptHelper.java

java
private DexoptResult dexoptPackageUsingArtService(
        InstallRequest installRequest) {
    final PackageSetting ps = installRequest.getScannedPackageSetting();
    PackageManagerLocal packageManagerLocal =
            LocalManagerRegistry.getManager(PackageManagerLocal.class);
    try (PackageManagerLocal.FilteredSnapshot snapshot =
            PackageManagerLocalImpl.withFilteredSnapshot(packageManagerLocal, ps)) {
        DexoptParams params = getDexoptParamsByInstallRequest(installRequest);
        return getArtManagerLocal().dexoptPackage(
                snapshot, ps.getPackageName(), params);
    }
}

FilteredSnapshot 是 profile 被消费时的重要安全边界。ART 不接收 PMS 的可变内部对象,而是接收限定到当前包视图的快照;快照关闭后,ART 请求仍由自己的 service 处理。这样可以避免安装线程与包扫描线程同时修改 PackageSetting 时产生不一致。

4. cur/ref 语义 ​

PMS 源码中没有把 cur 或 ref profile 解析成 Java 状态机。它们是 ART profile 生命周期中的两个存储语义:运行时产生的当前数据可以被 ART 汇总、合并为更稳定的参考数据,dexopt 再依据 ART 暴露的 profile 结果做编译决策。因此阅读 PMS 时应追踪三个接口,而不是寻找 profile 路径常量:

  1. ArtManagerLocal 的 profile snapshot/clear 操作;
  2. DexOptHelper.dexoptPackage 的消费入口;
  3. PackageManagerShellCommand 的调试转发。

如果只在 PMS 中搜索 .prof,会误以为 profile 采集缺失;实际设计是把格式、合并和文件布局封装在 ART Service,避免 Package Manager 依赖 ART 文件格式。

5. 热方法采集 ​

源码文件:art/runtime/jit/profile_saver.cc

cpp
auto get_method_flags = [&](ArtMethod& method) {
  // ART runtime 的 warm 方法在 profile 中记为 hot。
  if (IsMethodPreviouslyWarm(&method)) {
    ++number_of_hot_methods;
    return enum_cast<ProfileCompilationInfo::MethodHotness::Flag>(
        base_flags | Hotness::kFlagHot);
  } else if (method.CounterHasChanged(initial_value)) {
    ++number_of_sampled_methods;
    return enum_cast<ProfileCompilationInfo::MethodHotness::Flag>(base_flags);
  } else {
    return enum_cast<ProfileCompilationInfo::MethodHotness::Flag>(0u);
  }
};

ProfileSaver 并不是简单地把所有执行过的方法写入文件。它把已经达到 warm 条件的方法标记为 kFlagHot,把计数发生变化但尚未达到 warm 条件的方法作为 sampled 数据;启动阶段还会附加 kFlagStartup,启动完成后的采集则使用 kFlagPostStartup。这解释了 profile 为什么同时能服务启动优化和长期运行优化。

源码文件:art/runtime/jit/profile_saver.cc

cpp
while (!ShuttingDown(self)) {
  // 等待 JIT 通知,或按 profile saver 的周期退避唤醒。
  ProcessProfilingInfo(/*force_save=*/ false, &number_of_new_methods);
}

ProfileSaver::Run 在 startup 完成后进入循环,依据 JIT 通知和保存周期调用 ProcessProfilingInfo。在写盘前,它加载已有 profile、添加新的 profiled methods,并把缓存中的信息合并;遇到旧 dex 与当前代码不一致时会清空无效数据再保存。Profile 的“采集”和“合并”因此是 ART runtime 的状态更新,不是 PMS 的 Java 集合操作。

源码文件:art/runtime/jit/profile_saver.cc

cpp
// primary.prof 最后写入,artd 以此判断 profile save 是否完成。
std::sort(tracked_locations.begin(), tracked_locations.end(),
    [&](const auto& pair1, const auto& pair2) {
      return profile_to_code_type.Get(pair1.first) != AppInfo::CodeType::kPrimaryApk
          && profile_to_code_type.Get(pair2.first) == AppInfo::CodeType::kPrimaryApk;
    });

这个排序是一个容易被忽略的同步约定:primary.prof 被放到最后写入,artd 可以把它作为 profile 保存完成的观察点。读者在分析“snapshot 看到旧数据”时,不能只看 PMS 是否发出了请求,还要检查 runtime saver 是否完成了这一写盘顺序。

6. 生命周期清理 ​

6.1 清用户数据 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/AppDataHelper.java

java
void clearAppDataLIF(AndroidPackage pkg, int userId, int flags) {
    if (pkg == null) {
        return;
    }
    clearAppDataLeafLIF(pkg.getPackageName(), pkg.getVolumeUuid(), userId, flags);

    if ((flags & Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES) == 0) {
        clearAppProfilesLIF(pkg);
    }
}

清数据默认同时清 profile;只有显式设置 FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES 才保留。这个判断发生在 PMS 的数据清理路径,而不是 ART 自己猜测用户意图。

6.2 ART 尚未初始化 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/AppDataHelper.java

java
void destroyAppProfilesLIF(String packageName) {
    if (!DexOptHelper.artManagerLocalIsInitialized()) {
        // ART Service 尚未启动时跳过清理;ART/runtime 会忽略失效 profile。
        return;
    }

    try (PackageManagerLocal.FilteredSnapshot snapshot =
            getPackageManagerLocal().withFilteredSnapshot()) {
        try {
            DexOptHelper.getArtManagerLocal().clearAppProfiles(
                    snapshot, packageName);
        } catch (IllegalArgumentException e) {
            Slog.w(TAG, e);
        }
    }
}

这里的“跳过”是有条件的恢复策略,不是遗漏:PMS 构造期间可能已经触发初始化系统包,但 ART Service 还没有注册 PackageManagerLocal。源码明确依赖 ART/runtime 忽略 stale 或 invalid 的 cur/ref profile,因此不在初始化顺序不满足时强行调用 ART。

6.3 版本升级 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java

java
private void maybeClearProfilesForUpgradesLI(
        @Nullable PackageSetting originalPkgSetting,
        @NonNull AndroidPackage pkg) {
    if (originalPkgSetting == null || !mPm.isDeviceUpgrading()) {
        return;
    }
    if (originalPkgSetting.getVersionCode() == pkg.getLongVersionCode()) {
        return;
    }

    mAppDataHelper.clearAppProfilesLIF(pkg);
}

只有设备升级过程中且版本号变化时才清理这条路径上的 profile。原因是旧版本的代码布局可能与新版本不兼容;清理动作完成后,新版本运行时重新采集,后续 dexopt 再消费新 profile。

6.4 删除 dexopt 产物 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/DeletePackageHelper.java

java
private void deleteArtDexoptArtifacts(String packageName) {
    try (PackageManagerLocal.FilteredSnapshot filteredSnapshot =
            PackageManagerServiceUtils.getPackageManagerLocal()
                    .withFilteredSnapshot()) {
        try {
            DexOptHelper.getArtManagerLocal().deleteDexoptArtifacts(
                    filteredSnapshot, packageName);
        } catch (IllegalArgumentException | IllegalStateException e) {
            Slog.w(TAG, e.toString());
        }
    }
}

删除 dexopt artifacts 与清 profile 是两个动作:前者删除编译产物,后者清理 profile 数据。删除包时二者可能相邻发生,但不能把“没有 oat/artifacts”推断为“profile 已删除”。调试删除问题时要分别观察两个 ART Service 调用。

7. 后台消费 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/DexOptHelper.java

java
systemContext.registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        context.unregisterReceiver(this);
        artManager.scheduleBackgroundDexoptJob();
    }
}, new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED));

ART Service 在 locked boot complete 后安排后台 dexopt。安装期间的 dexopt 只处理安装请求;后台任务则根据设备状态、包集合和当前可用 profile 重新决策。于是 profile 的典型闭环是:应用运行时采集 → ART 保存/合并 → 后台 dexopt 读取 → 生成更合适的编译产物。

8. shell 兼容层 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java

java
// For backward compatibility. New ART Service commands belong to "art".
private static final Set<String> ART_SERVICE_COMMANDS = Set.of(
        "compile", "reconcile-secondary-dex-files", "force-dex-opt",
        "bg-dexopt-job", "cancel-bg-dexopt-job", "delete-dexopt",
        "dump-profiles", "snapshot-profile", "art");

这些命令仍可从 pm 命名空间进入,但实现会把输入转交给 ArtManagerLocal.handleShellCommand。当 ART Service 尚未注册时,命令返回“ART Service is not ready”,而不是由 PMS 自己伪造一个 profile 结果。新的 ART 命令应使用 art 命名空间,旧命令保留只是为了兼容已有脚本。

9. 失败与观测 ​

失败点PMS 的行为读者应观察什么
ART 未初始化清理 profile 直接跳过;shell 返回 not ready初始化顺序、LocalManagerRegistry
profile snapshot 错误回调 onError(errCode)错误码而不是空 FD
descriptor 复制失败callback 标记成功但本地 FD 为空/异常dup() 异常与 descriptor 生命周期
包在快照后消失IllegalArgumentException 被记录race,而非 profile 格式错误
dexopt 普通失败DexoptResult 返回失败状态,安装通常继续InstallRequest.onDexoptFinished

安装 dexopt 的普通失败不会自动阻断安装。DexOptHelper.performDexoptIfNeededAsync 还特别区分了“正常失败结果”和未预期异常:前者由 DexoptResult 表达,后者才记录 Slog.wtf。profile 不可用因此通常表现为编译降级或跳过,而不是包安装回滚。

10. 源码阅读路线 ​

  1. 从 DexOptHelper.initializeArtManagerLocal 看 ART owner 何时注册、后台任务何时安排。
  2. 阅读 PackageManagerShellCommand 的 ART_SERVICE_COMMANDS 和 snapshot callback,理解兼容命令与 FD 回调。
  3. 阅读 getDexoptParamsByInstallRequest,区分“忽略 profile”与“删除 profile”。
  4. 阅读 AppDataHelper.clearAppDataLIF/destroyAppProfilesLIF,确认清数据和初始化顺序的条件分支。
  5. 阅读 InstallPackageHelper.maybeClearProfilesForUpgradesLI 与 DeletePackageHelper.deleteArtDexoptArtifacts,分别追踪升级清理和产物删除。
  6. 最后进入 ART Service 的 profile/dexopt 实现,查看 profile 格式、merge 和实际编译策略;PMS 本身不会替你解释这些细节。