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.javaframeworks/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。
@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,而不是泄露存在性。
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。
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 和清缓存尚未完成;这是实现选择,不是状态不一致。
进入用户级休眠的后台方法会:
- 读取目标用户的
ApplicationInfo; - 通过
StorageStatsManager.queryStatsForPackage取得当前 cache bytes; - 在 app-restrictions flag 开启时通知 ActivityManager 进入
FORCE_STOPPED/DORMANT限制; - 调用
IActivityManager.forceStopPackage; - 调用
IPackageManager.deleteApplicationCacheFilesAsUser; - 把清理前的 cache bytes 写入
UserLevelState.savedByte。
注意 savedByte 是“本次休眠前可清理的缓存大小”,不是持续实时统计,也不是包含 APK/OAT 的总节省量。
5. 全局回收
源码文件:frameworks/base/services/core/java/com/android/server/apphibernation/AppHibernationService.java;相关符号:hibernatePackageGlobally、PackageManagerService.deleteOatArtifactsOfPackage。
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。
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。
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 目录下的状态文件。初始化先为当前已安装包创建默认非休眠状态,再覆盖磁盘中仍匹配的记录。
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 level | Environment.getDataSystemCeDirectory(userId)/hibernation | states | Proto |
| global level | Environment.getDataSystemDirectory()/hibernation | states | Proto |
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. 失败边界
| 现象 | 应检查的源码 | 解释 |
|---|---|---|
| 设置后查询仍为 false | DeviceConfig、权限、user unlock、canQueryPackage | 服务关闭、用户未解锁或包不可见都会短路 |
| 查询已为 true 但进程仍运行 | setHibernatingForUser 与后台 executor | 状态先改,force-stop 异步执行;应等待后台任务完成 |
| 解除后 alarms/jobs 没恢复 | unhibernatePackageForUser 的两个 boot 广播 | 应检查 receiver 是否声明 RECEIVE_BOOT_COMPLETED |
| 全局休眠没有释放 OAT | mOatArtifactDeletionEnabled、PMS 权限和 ART 删除结果 | 资源开关可能关闭,删除失败返回负值并按 0 计入 |
| 重启后休眠状态消失 | HibernationStateDiskStore 延迟写、AtomicFile、启动 reconcile | 旧快照可能被正常恢复逻辑覆盖 |
| 手动 unstop 后仍被视为休眠 | PMS setPackageStoppedState 回调 | 应确认 LocalServices 中的 AppHibernationManagerInternal 已注册 |
12. 源码练习
- 从
AppHibernationManager.setHibernatingForUser追到hibernatePackageForUser,标出 Binder 权限检查、状态锁、后台副作用和延迟写盘四个时点。 - 构造一个全局休眠包:说明
deleteOatArtifactsOfPackage如何经过PackageManagerInternal到 ART,并指出为什么savedByte只能代表本次删除返回值。 - 对比三个 UsageStats 事件测试,解释为什么
ACTIVITY_RESUMED能解除休眠,而未列出的事件不能据此推断。 - 给定“磁盘 hibernated=true,但应用没有
FLAG_STOPPED”的故障,定位initializeUserHibernationStates的修复分支,并说明它为何更新lastUnhibernatedMs。
