Skip to content

App Hibernation 应用休眠

追踪 Android 17 应用休眠的双层状态、缓存与 OAT 回收、持久化和自动恢复路径。

基于android-17.0.0_r1
AndroidPMSAppHibernationStorageDexOpt

App Hibernation 应用休眠 ​

本文面向已经了解 PackageManager、ActivityManager 和 UsageStats 基本职责的读者,重点解释 Android 17 中“应用休眠”如何从系统服务 API 变成可观察的资源回收结果。入口在 AppHibernationManager/IAppHibernationService,状态 owner 是 AppHibernationService,策略决定者则是 PermissionController;三者不能混为同一个模块。

本文不讨论 PermissionController 如何计算“长期未使用”,也不把休眠等同于卸载。读完后,读者应能定位用户级和全局级状态的存储位置,复述一次 setHibernatingForUser 的异步调用链,解释为什么解除休眠要重新发送启动广播,并判断磁盘上的陈旧状态何时会被 PMS 自动纠正。

1. 策略与执行 ​

源码文件:

  • frameworks/base/services/core/java/com/android/server/apphibernation/AppHibernationService.java
  • frameworks/base/core/java/android/apphibernation/AppHibernationManager.java

AppHibernationService 的类注释明确写着:服务管理休眠状态,而“决定哪些应用应该休眠”的策略由 PermissionController 负责。服务本身只接受带 MANAGE_APP_HIBERNATION 权限的调用,执行状态转换及其副作用。

服务还暴露 AppHibernationManagerInternal 的 LocalServices 接口,PMS 可以在应用被手动 unstop 时解除休眠。这条本地调用绕过 Binder,但仍进入同一个状态 owner 和权限/可见性逻辑。

2. 双层状态 ​

源码文件:frameworks/base/services/core/java/com/android/server/apphibernation/AppHibernationService.java。

java
@GuardedBy("mLock")
private final SparseArray<Map<String, UserLevelState>> mUserStates =
        new SparseArray<>();

@GuardedBy("mLock")
private final Map<String, GlobalLevelState> mGlobalHibernationStates =
        new ArrayMap<>();

UserLevelState 的 key 是 userId + packageName,字段包括 hibernated、该用户清理的 savedByte 和 lastUnhibernatedMs。GlobalLevelState 只按 packageName 建索引,记录包级 hibernated、删除 OAT 后的 savedByte 和恢复时间。

全局状态并不自动等价于“所有用户的 user 状态都为 true”。它是独立的包/APK 级优化标记;源码注释称它允许系统在包或 APK 层面做优化。用户级和全局级可以分别设置,也可以由同一个使用事件同时解除。

3. Binder 边界 ​

AppHibernationManager 是隐藏的 System API 客户端。每个用户 API 从 Context.getUserId() 取得调用用户,再通过 IAppHibernationService 发起 Binder 调用;所有读写方法声明 MANAGE_APP_HIBERNATION。

服务端在真正读取状态前还会检查三件事:DeviceConfig 是否启用、传入 user 是否处于 unlocking/unlocked、调用者是否能通过 PackageManagerInternal.canQueryPackage 看到目标包。服务关闭时查询返回空/false,写操作直接返回;包不可见时查询也返回空/false,而不是泄露存在性。

java
boolean isHibernatingGlobally(String packageName) {
    if (!sIsServiceEnabled) {
        return false;
    }
    getContext().enforceCallingOrSelfPermission(
            Manifest.permission.MANAGE_APP_HIBERNATION, ...);
    synchronized (mLock) {
        GlobalLevelState state = mGlobalHibernationStates.get(packageName);
        if (state == null || !mPackageManagerInternal.canQueryPackage(
                Binder.getCallingUid(), packageName)) {
            return false;
        }
        return state.hibernated;
    }
}

这也是一个重要的竞态边界:安装尚未完成、全局表还没有该包时,isHibernatingGlobally 会返回 false。调用者不能把这个 false 当作“包一定从未休眠”。

4. 用户级转换 ​

源码文件:frameworks/base/services/core/java/com/android/server/apphibernation/AppHibernationService.java;源码符号:setHibernatingForUser。

java
if (pkgState.hibernated == isHibernating) {
    return;
}

pkgState.hibernated = isHibernating;
if (isHibernating) {
    mBackgroundExecutor.execute(() ->
            hibernatePackageForUser(packageName, realUserId, pkgState));
} else {
    mBackgroundExecutor.execute(() ->
            unhibernatePackageForUser(packageName, realUserId));
    pkgState.lastUnhibernatedMs = System.currentTimeMillis();
}

mUserDiskStores.get(realUserId).scheduleWriteHibernationStates(
        new ArrayList<>(mUserStates.get(realUserId).values()));

状态位在 mLock 内先改变,昂贵的副作用放到后台 executor,持久化也只提交一份状态快照。这意味着查询可能先看到 hibernated=true,而 force-stop 和清缓存尚未完成;这是实现选择,不是状态不一致。

进入用户级休眠的后台方法会:

  1. 读取目标用户的 ApplicationInfo;
  2. 通过 StorageStatsManager.queryStatsForPackage 取得当前 cache bytes;
  3. 在 app-restrictions flag 开启时通知 ActivityManager 进入 FORCE_STOPPED/DORMANT 限制;
  4. 调用 IActivityManager.forceStopPackage;
  5. 调用 IPackageManager.deleteApplicationCacheFilesAsUser;
  6. 把清理前的 cache bytes 写入 UserLevelState.savedByte。

注意 savedByte 是“本次休眠前可清理的缓存大小”,不是持续实时统计,也不是包含 APK/OAT 的总节省量。

5. 全局回收 ​

源码文件:frameworks/base/services/core/java/com/android/server/apphibernation/AppHibernationService.java;相关符号:hibernatePackageGlobally、PackageManagerService.deleteOatArtifactsOfPackage。

java
private void hibernatePackageGlobally(String packageName,
        GlobalLevelState state) {
    long savedBytes = 0;
    if (mOatArtifactDeletionEnabled) {
        savedBytes = Math.max(
                mPackageManagerInternal.deleteOatArtifactsOfPackage(packageName), 0);
    }
    synchronized (mLock) {
        state.savedByte = savedBytes;
    }
}

是否删除 OAT 由资源布尔值 config_hibernationDeletesOatArtifactsEnabled 决定,并通过 isOatArtifactDeletionEnabled() 以受保护 API 暴露。PMS 的实现进一步要求 system/root/shell,并调用 ART 的 deleteDexoptArtifacts,将释放字节数返回给休眠服务。

全局休眠因此不是“再 force-stop 一次所有用户”。它的核心副作用是包级编译产物回收;用户级缓存清理仍由各 user 状态单独执行。全局解除时服务把 savedByte 置零并记录 lastUnhibernatedMs,不会在解除瞬间重新生成 OAT。

6. 解除与广播 ​

源码文件:frameworks/base/services/core/java/com/android/server/apphibernation/AppHibernationService.java;源码符号:unhibernatePackageForUser。

解除用户级休眠不仅把布尔值改回 false。该方法清除 Binder calling identity 后,向目标包发送两个受 RECEIVE_BOOT_COMPLETED 保护的广播:LOCKED_BOOT_COMPLETED 和 BOOT_COMPLETED。

java
Intent lockedBcIntent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
        .setPackage(packageName);
mIActivityManager.broadcastIntentWithFeature(..., lockedBcIntent, ...,
        requiredPermissions, ..., userId);

Intent bcIntent = new Intent(Intent.ACTION_BOOT_COMPLETED)
        .setPackage(packageName);
mIActivityManager.broadcastIntentWithFeature(..., bcIntent, ...,
        requiredPermissions, ..., userId);

原因在源码注释中写得很直接:应用需要重新注册 alarms、jobs 等启动后资源。休眠期间应用被 force-stop,解除后不能假设原来的进程和调度状态仍然存在。

PMS 还有一条反向入口:setPackageStoppedState(..., stopped=false) 提交 unstop 后,通过 handler 查询 AppHibernationManagerInternal;若仍处于用户级休眠,就同时解除用户级和全局级休眠。这避免“手动 unstop 但休眠位仍为 true”造成下次启动再次被系统视为休眠应用。

7. 使用事件 ​

源码文件:frameworks/base/services/core/java/com/android/server/apphibernation/AppHibernationService.java;字段:mUsageEventListener。

构造函数向 UsageStatsManagerInternal 注册 UsageEventListener。监听器只处理三类事件:USER_INTERACTION、ACTIVITY_RESUMED 和 APP_COMPONENT_USED。

java
private final UsageEventListener mUsageEventListener = (userId, event) -> {
    if (!isAppHibernationEnabled()) {
        return;
    }
    int eventType = event.mEventType;
    if (eventType == UsageEvents.Event.USER_INTERACTION
            || eventType == UsageEvents.Event.ACTIVITY_RESUMED
            || eventType == UsageEvents.Event.APP_COMPONENT_USED) {
        String pkgName = event.mPackage;
        setHibernatingForUser(pkgName, userId, false);
        setHibernatingGlobally(pkgName, false);
    }
};

这条路径把“第一次使用”定义成解除休眠,而不是等待应用自行调用管理 API。测试分别以三种事件作为输入,并断言 user/global 查询均变为 false;因此可以确认的是这三类事件,不能外推为所有 UsageStats 事件都会解除休眠。

8. 启动与用户流程 ​

全局状态在 PHASE_BOOT_COMPLETED 后由后台线程读取;用户状态则在 onUserUnlocking 中读取对应 CE 目录下的状态文件。初始化先为当前已安装包创建默认非休眠状态,再覆盖磁盘中仍匹配的记录。

java
if (pkgInfo.applicationInfo != null
        && (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) == 0
        && currentState.hibernated) {
    // 磁盘状态陈旧:包没有被 force-stop,不能继续宣称休眠
    currentState.hibernated = false;
    currentState.lastUnhibernatedMs = System.currentTimeMillis();
}

这段恢复逻辑是一个关键不变量:磁盘上的 hibernated=true 必须和 PMS 的 stopped 状态相容。测试 testNonForceStoppedAppsNotHibernatedOnUnlock 构造了“磁盘说休眠、PackageManager 没有 stopped flag”的输入,断言解锁后查询为 false。

如果用户解锁后发现某个包的 user 状态不是休眠,但全局状态仍是休眠,onUserUnlocking 会调用 setHibernatingGlobally(pkgName, false)。新增包通过 ACTION_PACKAGE_ADDED 加入两张表;更新过程带 EXTRA_REPLACING 时不修改状态;按用户移除只删除 user 表项,EXTRA_REMOVED_FOR_ALL_USERS 才删除全局表项。

9. 状态持久化 ​

源码文件:frameworks/base/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java。

状态目录文件格式
user levelEnvironment.getDataSystemCeDirectory(userId)/hibernationstatesProto
global levelEnvironment.getDataSystemDirectory()/hibernationstatesProto

scheduleWriteHibernationStates 不立即写盘,而是延迟 1 分钟;同一 store 已经有任务时,后续快照覆盖待写数据。真正写入使用 AtomicFile.startWrite/finishWrite,异常时 failWrite,避免半个 protobuf 覆盖旧文件。

因此有两个时间差:内存状态立即变化,磁盘状态最多延迟约一分钟;设备在延迟窗口内异常掉电,重启可能读到旧状态。启动时的 package/stopped reconciliation 是对此风险的恢复措施。测试 testScheduleWriteHibernationStates_laterWritesOverwritePrevious 证明后提交的完整快照会覆盖先前待写快照。

10. 统计与可见性 ​

getHibernatingPackagesForUser 和 getHibernationStatsForUser 都在返回前调用 canQueryPackage,所以结果是调用者可见包的子集。统计中的 diskBytesSaved 等于 global savedByte 加 user savedByte;缺少任一层状态的包会被跳过并写 warning。

服务在 PHASE_SYSTEM_SERVICES_READY 注册 StatsManager pull atom:用户级和全局级休眠状态分别进入 FrameworkStatsLog。 dump() 则要求 usage-stats dump 权限,并打印每个 user 的 UserLevelState 和全局状态。调试时应把“服务 dump 中的状态”“stats 中的汇总”和“磁盘真实占用”区分开,三者并非同一采样时刻。

11. 失败边界 ​

现象应检查的源码解释
设置后查询仍为 falseDeviceConfig、权限、user unlock、canQueryPackage服务关闭、用户未解锁或包不可见都会短路
查询已为 true 但进程仍运行setHibernatingForUser 与后台 executor状态先改,force-stop 异步执行;应等待后台任务完成
解除后 alarms/jobs 没恢复unhibernatePackageForUser 的两个 boot 广播应检查 receiver 是否声明 RECEIVE_BOOT_COMPLETED
全局休眠没有释放 OATmOatArtifactDeletionEnabled、PMS 权限和 ART 删除结果资源开关可能关闭,删除失败返回负值并按 0 计入
重启后休眠状态消失HibernationStateDiskStore 延迟写、AtomicFile、启动 reconcile旧快照可能被正常恢复逻辑覆盖
手动 unstop 后仍被视为休眠PMS setPackageStoppedState 回调应确认 LocalServices 中的 AppHibernationManagerInternal 已注册

12. 源码练习 ​

  1. 从 AppHibernationManager.setHibernatingForUser 追到 hibernatePackageForUser,标出 Binder 权限检查、状态锁、后台副作用和延迟写盘四个时点。
  2. 构造一个全局休眠包:说明 deleteOatArtifactsOfPackage 如何经过 PackageManagerInternal 到 ART,并指出为什么 savedByte 只能代表本次删除返回值。
  3. 对比三个 UsageStats 事件测试,解释为什么 ACTIVITY_RESUMED 能解除休眠,而未列出的事件不能据此推断。
  4. 给定“磁盘 hibernated=true,但应用没有 FLAG_STOPPED”的故障,定位 initializeUserHibernationStates 的修复分支,并说明它为何更新 lastUnhibernatedMs。