Android15 AndroidV冻结和解冻的场景
摘要
cgroup 最初由 Google 工程师 Paul Menage 和 Rohit Seth 在 2006 年提出,是一种细粒度资源控制的Linux内核机制。于 2007 年合并到 Linux 内核主线中。然而 Google 原生系统直到 Android 11 或更高版本上才支持 CACHE 应用的 CPU 冻结功能。当应用切换到后台并且没有其他活动时,系统会在一定时间内通过状态判断,将进程 ID 迁移到冻结的 cgroup 节点上,实现冻结 CACHE 应用。这项功能可以减少活跃缓存应用在后台存在时所消耗的 CPU 资源,从而达到节电的目的。当应用再次切换到前台时,系统会将该应用的进程解冻,以实现快速启动。
一、冻结场景
主要看哪些场景调用到 freezeAppAsyncInternalLSP 函数
冻结场景 | 触发条件 | 执行逻辑 | 操作示例/命令 |
Adj更新触发冻结 | 进程的OOM_ADJ值≥CACHED_APP_MIN_ADJ(900)且持续10秒无交互 | 系统通过CachedAppOptimizer.freezeAppAsyncLSP延迟10秒执行冻结 | 自动触发,无需手动操作 |
广播发送方冻结 | 发送方进程(callerApp)满足isProcessFreezable条件(adj≥900且非豁免) | 1. 检查广播发送数量阈值 2. 调用forceFreezeAppAsyncLSP立即冻结 | 代码逻辑:BroadcastQueueModernImpl.enqueueBroadcastLocked() |
ADB命令手动冻结 | 通过am freeze命令指定目标进程 | 1. 解析包名/PID 2. 调用runFreeze(pw, true)写入cgroup.freeze=1 | adb shell am freeze com.tencent.mp adb shell am freeze --pid 9432 |
1.1 Adj 更新触发大于900判断,10秒后执行冻结
冻结流程:前台进程 --> 可感知进程--> 服务进程 --> Cached进程->10秒后->cgroup freeze子系统冻结
CachedAppOptimizer.freezeAppAsyncLSP(ProcessRecord app, @UptimeMillisLong long delayMillis) // 10 秒后冻结
->CachedAppOptimizer.freezeAppAsyncInternalLSP(ProcessRecord app, @UptimeMillisLong long delayMillis, boolean force)
ActivityManagerService.updateOomAdjLocked
→OomAdjuster.updateOomAdjLocked
→→OomAdjuster.updateOomAdjLSP
→→→OomAdjusterModernImpl.performUpdateOomAdjLSP
→→→→OomAdjusterModernImpl.performUpdateOomAdjPendingTargetsLocked
→→→→→OomAdjusterModernImpl.partialUpdateLSP
→→→→→→OomAdjuster.postUpdateOomAdjInnerLSP
→→→→→→→OomAdjuster.updateAndTrimProcessLSP
→→→→→→→→OomAdjuster.applyOomAdjLSP
→→→→→→→→→OomAdjuster.updateAppFreezeStateLSP adj大于900进程(FREEZER_CUTOFF_ADJ)
→→→→→→→→→→CachedAppOptimizer.freezeAppAsyncLSP
→→→→→→→→→→→CachedAppOptimizer.freezeAppAsyncInternalLSP 防抖超时阈值(默认10秒)
→→→→→→→→→→→→SET_FROZEN_PROCESS_MSG 等10秒(默认10秒防抖)
→→→→→→→→→→→→→CachedAppOptimizer.freezeBinder 先冻结Binder再冻结进程的原则
→→→→→→→→→→→→→CachedAppOptimizer.freezeProcess 通过cgroup v2 freezer子系统实现
→→→→→→→→→→→→→→Process.setProcessFrozen(pid, proc.uid, true) 内核级冻结
→→→→→→→→→→→→→→→android_os_Process.android_os_Process_setProcessFrozen(jint pid, jint uid, jboolean freeze)
→→→→→→→→→→→→→→→→processgroup.SetProcessProfiles
→→→→→→→→→→→→→→→→→TaskProfiles.SetProcessProfiles()
→→→→→→→→→→→→→→→→→→TaskProfiles.ExecuteForProcess()
→→→→→→→→→→→→→→→→→→→TaskProfiles.ExecuteForProcess()写冻结的文件节点:/sys/fs/cgroup/uid_1000/pid_1234/cgroup.freeze,例如1
1.2 广播发送方若可被冻结,则立刻冻结
防止广播发送方滥用后台资源
当广播发送方进程(callerApp)处于缓存状态(adj≥900)时,若其频繁发送广播,可能通过跨进程通信(Binder)持续占用CPU资源。冻结发送方进程可强制中断其广播发送能力,避免后台进程通过广播保活或消耗系统资源
BroadcastQueueModernImpl.enqueueBroadcastLocked
->->CachedAppOptimizer.freezeAppAsyncImmediateLSP(ProcessRecord app) // 立即冻结
->->->CachedAppOptimizer.freezeAppAsyncInternalLSP(ProcessRecord app, @UptimeMillisLong long delayMillis, boolean force)
/**
* 处理广播入队逻辑,并对后台进程的广播发送行为进行限制
*
* 核心设计原则:
* 1. 选择性冻结广播发送方(callerApp)而非接收方,原因:
* - 发送方通常是后台进程(adj≥900),冻结可防止其滥用广播保活
* - 接收方多为用户交互进程(adj≤700),冻结会导致功能中断
* - 符合Android非对称优先级策略:牺牲后台进程保前台体验
*
* 2. 异步冻结机制确保系统稳定性:
* - freezeAppAsyncImmediateLSP的延迟执行(默认0秒)避免误杀临时性广播
* - 超过MAX_FROZEN_OUTGOING_BROADCASTS阈值时立即终止进程
*/
@Override
public void enqueueBroadcastLocked(@NonNull BroadcastRecord r) {
// TODO: Apply delivery group policies and FLAG_REPLACE_PENDING to collapse the
// outgoing broadcasts.
// TODO: Add traces/logs for the enqueueing outgoing broadcasts logic.
/* 后台进程广播限制逻辑
* 触发条件:
* 1. 系统启用广播延迟策略(Flags.deferOutgoingBroadcasts)
* 2. 发送方为可冻结进程(adj≥900且非豁免状态)
*/
if (Flags.deferOutgoingBroadcasts() && isProcessFreezable(r.callerApp)) {
// 获取或创建进程专属的广播队列
final BroadcastProcessQueue queue = getOrCreateProcessQueue(
r.callerApp.processName, r.callerApp.uid);
/* 广播流量控制
* 当单个后台进程发送广播超过阈值时(防DoS攻击):
* 1. 记录异常日志(noisy=true触发Log.wtf)
* 2. 立即终止进程而非冻结,避免资源持续泄漏
*
* 典型阈值:MAX_FROZEN_OUTGOING_BROADCASTS=5(机型可配置)
*/
if (queue.getOutgoingBroadcastCount() >= mConstants.MAX_FROZEN_OUTGOING_BROADCASTS) {
r.callerApp.killLocked("Too many outgoing broadcasts in cached state",
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_EXCESSIVE_OUTGOING_BROADCASTS_WHILE_CACHED,
true /* noisy */);
return;
}
// 记录广播到进程历史(用于ANR分析和状态恢复)
queue.enqueueOutgoingBroadcast(r);
mHistory.onBroadcastFrozenLocked(r);
/* 执行异步冻结
* 关键特性:
* 1. 延迟10秒执行,允许短时突发广播
* 2. 通过Binder冻结+内存压缩双重优化
* 3. 与OomAdjuster协同更新adj值
*/
mService.mOomAdjuster.mCachedAppOptimizer.freezeAppAsyncImmediateLSP(r.callerApp);
return;
}
}
1.3 adb shell 命令冻结
冻结命令
基础命令格式:adb shell am freeze[options]
:目标应用的包名(如 com.example.app)或进程ID(如 1234)
options]:可选参数(如 --user 0指定用户)
例如
adb shell am freeze com.tencent.mp
adb shell am freeze --pid 9432
ActivityManagerShellCommand.runFreeze
->CachedAppOptimizer.forceFreezeAppAsyncLSP(ProcessRecord app)
/**
* 执行进程冻结/解冻操作的命令行接口实现
*
* @param pw 输出流,用于打印操作结果
* @param freeze 操作类型:true=冻结,false=解冻
* @return 执行状态:0=成功,-1=失败
* @throws RemoteException 远程调用异常
*
* 关键流程说明:
* 1. 解析命令行参数(如--sticky标记)
* 2. 获取目标进程记录(ProcessRecord)
* 3. 执行冻结/解冻操作并输出结果
*/
@NeverCompile // 标记该方法不参与编译时优化
int runFreeze(PrintWriter pw, boolean freeze) throws RemoteException {
// 解析命令行附加参数(如--sticky持久化标记)
String freezerOpt = getNextOption();
boolean isSticky = false;
if (freezerOpt != null) {
isSticky = freezerOpt.equals("--sticky");
}
// 通过shell命令获取目标进程信息
ProcessRecord proc = getProcessFromShell();
if (proc == null) {
return -1; // 进程不存在或权限不足
}
// 打印操作日志(包含进程名、PID和sticky状态)
pw.print(freeze ? "Freezing" : "Unfreezing");
pw.print(" process " + proc.processName);
pw.println(" (" + proc.mPid + ") sticky=" + isSticky);
/* 核心冻结/解冻逻辑(需双重锁保护)
* 同步块说明:
* 1. mInternal锁:保证AMS服务状态一致性
* 2. mProcLock锁:防止进程状态并发修改
*/
synchronized (mInternal) {
synchronized (mInternal.mProcLock) {
// 设置持久化标记(控制解冻后是否保持低优先级)
proc.mOptRecord.setFreezeSticky(isSticky);
if (freeze) {
/* 强制异步冻结
* 内部流程:
* 1. 延迟10ms执行(避免锁竞争)
* 2. 调用Binder驱动冻结IPC通信
* 3. 写入cgroup.freeze=1触发内核冻结
*/
mInternal.mOomAdjuster.mCachedAppOptimizer.forceFreezeAppAsyncLSP(proc);
} else {
/* 内部解冻
* 参数说明:
* @param proc 目标进程
* @param reason 解冻原因(0=手动触发)
* @param immediate 是否跳过延迟立即执行
*/
mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(proc, 0, true);
}
}
}
return 0; // 操作成功
}
二、解冻场景
场景分类 | 触发条件 | 解冻原因常量 | 解冻持续时间 | 典型命令/场景 |
广播机制 | warm进程接收广播 | UNFREEZE_REASON_START_RECEIVER | 默认10秒 | 广播分发时自动触发 |
广播配置TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED标记 | UNFREEZE_REASON_START_RECEIVER | 按options配置时长 | 高优先级推送广播 | |
有序广播结果回传 | UNFREEZE_REASON_FINISH_RECEIVER | 固定10秒 | scheduleResultTo()调用 | |
广播队列标记runningOomAdjusted=true | UNFREEZE_REASON_START_RECEIVER | 默认10秒 | notifyStartedRunning()触发 | |
UI资源清理 | 进程处于IMPORTANT_BACKGROUND状态且hasPendingUiClean=true | UNFREEZE_REASON_TRIM_MEMORY | 按内存压力动态调整 | Activity.onStop()时触发 |
AMS机制 | 未完成Binder事务或系统ping检查 | UNFREEZE_REASON_PING | 默认10秒 | waitForApplicationBarrier()调用 |
Binder异常 | Binder冻结失败或冻结后仍有事务 | UNFREEZE_REASON_BINDER | 立即解冻不重冻 | handleBinderFreezerFailure()触发 |
文件锁冲突 | 冻结进程持有文件锁阻塞前台进程 | UNFREEZE_REASON_FILE_LOCKS | 永久解冻直到锁释放 | 内核回调onBlockingFileLock() |
ADB命令 | 手动执行解冻命令 | UNFREEZE_REASON_SHELL | 永久解冻 | adb shell am unfreeze com.tencent.mp |
主要看 unfreezeAppInternalLSP 的调用关系
解冻场景
CachedAppOptimizer.enableFreezer(boolean enable) 开发者选项的冻结功能停用按钮触发解冻场景
CachedAppOptimizer.unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason, long delayMillis)
CachedAppOptimizer.handleBinderFreezerFailure(final ProcessRecord proc, final String reason)
CachedAppOptimizer.onBlockingFileLock(IntArray pids)
ActivityManagerShellCommand.runFreeze("Unfreezing") adb shell 命令解冻场景
->CachedAppOptimizer.unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason)
->->CachedAppOptimizer.unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force)
2.1 开发者选项的冻结功能停用按钮触发解冻场景
具体冻结机制开关对应 Settings.Global.CACHED_APPS_FREEZER_ENABLED 数值
adb shell settings get global cached_apps_freezer_enabled
adb shell settings put global cached_apps_freezer_enabled
->CachedAppOptimizer.enableFreezer(boolean enable)
->->CachedAppOptimizer.unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason)
->->->CachedAppOptimizer.unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force)
/**
* 动态更新冻结功能开关状态,并初始化相关资源
*
* 核心功能:
* 1. 从DeviceConfig和全局设置中读取冻结功能开关状态
* 2. 根据配置初始化冻结参数(如防抖时间、豁免列表)
* 3. 异步启动冻结管理线程(避免持有AMS主锁)
*
* 线程安全:
* - 通过@GuardedBy确保方法在mPhenotypeFlagLock保护下执行
* - 使用Handler异步处理资源初始化
*/
@GuardedBy("mPhenotypeFlagLock")
private void updateUseFreezer() {
// 从Settings.Global读取强制覆盖配置(最高优先级)
final String configOverride = Settings.Global.getString(
mAm.mContext.getContentResolver(),
Settings.Global.CACHED_APPS_FREEZER_ENABLED
);
/* 配置决策树(三级优先级):
* 1. 强制禁用(Settings.Global="disabled")
* 2. 强制启用(Settings.Global="enabled")或DeviceConfig允许
* 3. 默认禁用
*/
if ("disabled".equals(configOverride)) {
mUseFreezer = false; // 全局关闭冻结功能
} else if ("enabled".equals(configOverride) ||
DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_USE_FREEZER,
DEFAULT_USE_FREEZER
)) {
// 检查硬件支持并更新子配置
mUseFreezer = isFreezerSupported(); // 检测cgroup freezer和内核支持
updateFreezerDebounceTimeout(); // 更新防抖时间(默认10秒)
updateFreezerExemptInstPkg(); // 更新安装器包名豁免列表
} else {
mUseFreezer = false; // 默认关闭
}
// 通过Handler异步执行资源初始化(避免同步持锁)
final boolean useFreezer = mUseFreezer;
mAm.mHandler.post(() -> {
if (useFreezer) {
Slog.d(TAG_AM, "Freezer enabled");
enableFreezer(true); // 实际启用冻结功能
/* 初始化冻结管理线程
* - 单例线程,负责执行延迟冻结/解冻操作
* - 绑定到SYSTEM线程组(提高调度优先级)
*/
if (!mCachedAppOptimizerThread.isAlive()) {
mCachedAppOptimizerThread.start();
}
// 初始化冻结任务处理器(含延迟队列)
if (mFreezeHandler == null) {
mFreezeHandler = new FreezeHandler();
}
// 设置线程调度策略(提升为系统关键线程)
Process.setThreadGroupAndCpuset(
mCachedAppOptimizerThread.getThreadId(),
Process.THREAD_GROUP_SYSTEM
);
} else {
Slog.d(TAG_AM, "Freezer disabled");
enableFreezer(false); // 关闭所有冻结功能
}
});
}
2.2 临时解冻场景
2.2.1 广播机制中的临时解冻场景
warm进程收广播,触发临时解冻
广播配置豁免TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED标记,触发临时解冻
确保传递有序广播正常,触发临时解冻
广播队列标记为runningOomAdjusted=true ,触发临时解冻
->BroadcastQueueModernImpl.updateRunningListLocked warm进程收广播,触发解冻
->BroadcastQueueModernImpl.dispatchReceivers 广播配置豁免TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED标记,触发解冻
->BroadcastQueueModernImpl.scheduleResultTo 确保传递有序广播正常,触发解冻
->BroadcastQueueModernImpl.notifyStartedRunning 广播队列标记为runningOomAdjusted=true ,触发解冻
->->CachedAppOptimizer.unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason, long delayMillis)
->->->CachedAppOptimizer.unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force)
/**
* 更新广播队列运行状态,管理进程解冻/冻结状态
*
* 核心逻辑:将符合条件的runnable队列提升为running状态
* 重点关注:解冻条件出现在处理warm进程时
*/
@GuardedBy("mService")
private void updateRunningListLocked() {
// ... [资源计算和初始化部分省略] ...
// 遍历所有可运行(runnable)的广播队列
BroadcastProcessQueue queue = mRunnableHead;
while (queue != null && avail > 0) {
BroadcastProcessQueue nextQueue = queue.runnableAtNext;
// --- 触发解冻的核心条件判断 ---
if (queue.isProcessWarm()) { // 检查是否为warm进程(已缓存但未冻结)
/* 解冻条件:
* 1. 进程处于warm状态(adj=900-999)
* 2. 队列有待处理的广播(isRunnable=true)
* 3. 未超过MAX_RUNNING_PROCESS_QUEUES限制
*/
mService.mOomAdjuster.unfreezeTemporarily(queue.app,
CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
// 解冻后二次检查进程状态(可能因解冻失败被杀死)
if (!queue.isProcessWarm()) {
continue; // 进程已死亡则跳过
}
// 执行广播投递(warm进程快速响应)
scheduleReceiverWarmLocked(queue);
}
else {
// 冷启动处理(非冻结进程,无需解冻)
scheduleReceiverColdLocked(queue);
}
// ... [后续处理逻辑省略] ...
}
// ... [状态更新和清理逻辑省略] ...
}
/**
* 分发广播到目标接收器,处理ANR计时和进程解冻逻辑
*
* @param queue 目标进程的广播队列
* @param r 待分发的广播记录
* @param index 接收器在列表中的位置
* @return 是否为阻塞式分发(需等待接收器完成)
*
* 核心逻辑:
* 1. 设置ANR超时监控(非豁免广播)
* 2. 处理后台启动权限
* 3. 根据广播配置触发进程解冻
*/
@GuardedBy("mService")
private boolean dispatchReceivers(@NonNull BroadcastProcessQueue queue,
@NonNull BroadcastRecord r, int index) throws BroadcastRetryException {
// --- 1. ANR监控设置 ---
if (mService.mProcessesReady && !r.timeoutExempt && !r.isAssumedDelivered(index)) {
startDeliveryTimeoutLocked(queue, r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT);
}
// --- 2. 后台启动权限处理 ---
if (r.mBackgroundStartPrivileges.allowsAny()) {
app.addOrUpdateBackgroundStartPrivileges(r, r.mBackgroundStartPrivileges);
// ... [设置超时回调省略] ...
}
// --- 3. 解冻条件判断(重点)---
if (r.options != null && r.options.getTemporaryAppAllowlistDuration() > 0) {
if (r.options.getTemporaryAppAllowlistType() ==
PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED) {
/* 触发解冻的核心条件:
* 1. 广播携带临时白名单配置(options不为null)
* 2. 白名单类型为APP_FREEZING_DELAYED(延迟冻结类型)
* 3. 白名单持续时间>0(getTemporaryAppAllowlistDuration)
*
* 解冻参数:
* - 目标进程:queue.app
* - 原因:UNFREEZE_REASON_START_RECEIVER
* - 持续时间:白名单配置的时长
*/
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
app,
CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER,
r.options.getTemporaryAppAllowlistDuration() // 解冻持续时间
);
} else {
// 其他白名单类型走常规处理
mService.tempAllowlistUidLocked(...);
}
}
// --- 4. 广播分发执行 ---
try {
if (receiver instanceof BroadcastFilter) {
thread.scheduleRegisteredReceiver(...);
} else {
thread.scheduleReceiver(...); // 动态注册接收器
}
return true;
} catch (RemoteException e) {
app.killLocked(...); // 进程通信失败时终止
throw new BroadcastRetryException(e);
}
}
/**
* 为有序广播安排最终结果回传(发送给resultTo指定的接收器)
*
* 触发条件:
* 1. 广播是有序广播(resultTo != null)
* 2. 发送方进程仍处于warm状态(adj=900-999)
* 3. 接收方进程存活(thread != null)
*
* 关键操作:
* - 临时解冻接收方进程确保结果能送达
* - 处理跨进程身份共享
* - 清理结果接收器引用防止内存泄漏
*/
@GuardedBy("mService")
private void scheduleResultTo(@NonNull BroadcastRecord r) {
// 1. 基础检查:非有序广播直接返回
if (r.resultTo == null) return;
// 2. 获取接收方进程状态
final ProcessRecord app = r.resultToApp;
final IApplicationThread thread = (app != null) ? app.getOnewayThread() : null;
if (thread != null) {
/* 核心解冻条件(同时满足才会解冻):
* - 接收方进程处于冻结状态(isFrozen=true)
* - 需要传递有序广播结果(resultTo非空)
* - 接收方Binder通道存活(thread != null)
*
* 解冻参数:
* - 原因:UNFREEZE_REASON_FINISH_RECEIVER(完成接收)
* - 默认持续时间:10秒(见CachedAppOptimizer.TEMP_UNFREEZE_DURATION_MS)
*/
mService.mOomAdjuster.unfreezeTemporarily(
app, CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
// 3. 处理跨进程身份共享(如ContentProvider调用链)
if (r.shareIdentity && app.uid != r.callingUid) {
mService.mPackageManagerInt.grantImplicitAccess(...);
}
// 4. 执行结果回传
try {
thread.scheduleRegisteredReceiver(...);
} catch (RemoteException e) {
// 进程通信失败时终止目标进程
app.killLocked(...);
}
}
// 5. 清理结果接收器引用(防内存泄漏)
r.resultTo = null;
}
/**
* 通知系统其他部分广播队列已开始运行(用于内部状态维护)
*
* 核心操作:
* 1. 更新进程接收器计数
* 2. 调整进程LRU位置(非受限进程)
* 3. 临时解冻目标进程
* 4. 强制更新进程状态(若需要)
*/
@GuardedBy("mService")
private void notifyStartedRunning(@NonNull BroadcastProcessQueue queue) {
if (queue.app != null) {
// 1. 增加当前接收器计数(用于ANR检测和状态跟踪)
queue.app.mReceivers.incrementCurReceivers();
/* 2. 调整LRU位置(除非进程处于后台受限状态)
* 受限状态示例:省电模式下的后台进程
*/
if (mService.mInternal.getRestrictionLevel(queue.uid)
< ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
mService.updateLruProcessLocked(queue.app, false, null);
}
/* --- 核心解冻条件 ---
* 触发解冻需同时满足:
* - 目标进程处于冻结状态(isFrozen=true)
* - 进程需要处理广播(queue.runningOomAdjusted=true)
* - 进程未被标记为豁免冻结(!app.mOptRecord.isFreezeExempt())
*
* 解冻参数:
* - 原因:UNFREEZE_REASON_START_RECEIVER(广播接收启动)
* - 默认持续时间:10秒(见CachedAppOptimizer.TEMP_UNFREEZE_DURATION_MS)
*/
mService.mOomAdjuster.unfreezeTemporarily(
queue.app,
CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER
);
// 3. 强制提升进程状态至RECEIVER级别(adj=700)
if (queue.runningOomAdjusted) {
queue.app.mState.forceProcessStateUpTo(
ActivityManager.PROCESS_STATE_RECEIVER
);
mService.enqueueOomAdjTargetLocked(queue.app);
}
}
}
2.2.2 进程存在未清理的UI资源的场景触发临时解冻
进程处于重要后台状态(PROCESS_STATE_IMPORTANT_BACKGROUND及以上)或系统无UI状态)且进程有未清理的UI资源标记(hasPendingUiClean),触发解冻。
->AppProfiler.scheduleTrimMemoryLSP
->->CachedAppOptimizer.unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason, long delayMillis)
->->->CachedAppOptimizer.unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force)
/**
* 检查并触发UI隐藏时的内存修剪(需持有mService和mProcLock锁)
*
* 解冻条件:
* 1. 进程处于重要后台状态(PROCESS_STATE_IMPORTANT_BACKGROUND及以上)或系统无UI状态
* 2. 进程有未清理的UI资源标记(hasPendingUiClean)
*
* 注:该方法通常由ActivityStack调用,当Activity进入onStop时触发
*/
@GuardedBy({"mService", "mProcLock"})
private void trimMemoryUiHiddenIfNecessaryLSP(ProcessRecord app) {
// 复合条件判断:进程状态+UI标记
if ((app.mState.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
|| app.mState.isSystemNoUi()) && app.mProfile.hasPendingUiClean()) {
/* 触发UI隐藏级别的内存修剪
* 使用TRIM_MEMORY_UI_HIDDEN(级别20):
* - 释放Activity中View树/Texture等资源
* - 通知Glide等图片库清除缓存
*/
scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN,
"Trimming memory of bg-ui ");
// 清除UI待清理标记(避免重复处理)
app.mProfile.setPendingUiClean(false);
}
}
/**
* 执行内存修剪调度(含解冻逻辑)
*
* 解冻核心条件:
* 1. 新请求的内存修剪级别高于当前记录值(getTrimMemoryLevel < level)
* 2. 进程Binder通信通道存活(getThread != null)
* 3. 进程当前处于冻结状态(由unfreezeTemporarily内部检查)
*
* @param level 内存修剪级别(参见ComponentCallbacks2常量)
* @param msg 调试日志前缀
*/
@GuardedBy({"mService", "mProcLock"})
private void scheduleTrimMemoryLSP(ProcessRecord app, int level, String msg) {
IApplicationThread thread;
// 双重条件检查(内存级别+进程存活)
if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) {
try {
// 调试日志输出
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
Slog.v(TAG_OOM_ADJ, msg + app.processName + " to " + level);
}
/* 关键解冻操作:
* 1. 临时提升进程优先级(adj=VISIBLE_APP_ADJ)
* 2. 清除cgroup.freeze标记
* 3. 设置10秒冻结延迟(TEMP_UNFREEZE_DURATION_MS)
*/
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
CachedAppOptimizer.UNFREEZE_REASON_TRIM_MEMORY);
// 通过Binder跨进程调用执行内存修剪
thread.scheduleTrimMemory(level);
} catch (RemoteException e) {
// 忽略进程已死亡的异常(Binder通道断开)
}
}
}
什么时候会解冻一个冻结的APP?
当同时满足以下两个情况时:
1.这个APP现在"不活跃"了:
要么已经退到后台(比如你按了Home键)
要么是系统服务且当前不需要显示界面(比如后台运行的天气服务)
2.这个APP之前用过界面资源还没收拾干净:
比如还留着没释放的图片缓存、界面元素等
2.2.3 AMS机制中的临时解冻场景
进程有未完成的Binder事务或系统需要确保进程能立即响应ping,触发临时解冻场景
->ActivityManagerService.waitForApplicationBarrier
->->CachedAppOptimizer.unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason, long delayMillis)
->->->CachedAppOptimizer.unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force)
/**
* 等待所有正在运行的应用程序处理完挂起的跨进程事件(Binder调用)
*
* 典型场景:系统执行关键操作(如OTA升级、内存压缩)前,确保所有进程完成当前任务
*/
void waitForApplicationBarrier(@NonNull PrintWriter pw) {
// 同步工具:用于等待所有进程响应
final CountDownLatch finishedLatch = new CountDownLatch(1);
// 计数器:记录发送的ping请求数和返回的pong响应数
final AtomicInteger pingCount = new AtomicInteger(0);
final AtomicInteger pongCount = new AtomicInteger(0);
// pong回调:当进程完成事件处理时触发
final RemoteCallback pongCallback = new RemoteCallback((result) -> {
if (pongCount.incrementAndGet() == pingCount.get()) {
finishedLatch.countDown(); // 所有响应到达时解除阻塞
}
});
// 先发一个哨兵ping(防止空进程导致立即完成)
pingCount.incrementAndGet();
/* 阶段1:向所有进程发送ping请求(需持有AMS和进程锁) */
synchronized (ActivityManagerService.this) {
synchronized (mProcLock) {
// 遍历所有运行中的进程
final ArrayMap<String, SparseArray<ProcessRecord>> pmap =
mProcessList.getProcessNamesLOSP().getMap();
for (int iProc = 0; iProc < pmap.size(); iProc++) {
final SparseArray<ProcessRecord> apps = pmap.valueAt(iProc);
for (int iApp = 0; iApp < apps.size(); iApp++) {
final ProcessRecord app = apps.valueAt(iApp);
final IApplicationThread thread = app.getOnewayThread();
if (thread != null) {
/* 关键解冻操作(触发条件):
* 1. 进程当前处于冻结状态(isFrozen=true)
* 2. 进程有未完成的Binder事务(outstanding_txns>0)
* 3. 系统需要确保进程能立即响应ping
*/
mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
CachedAppOptimizer.UNFREEZE_REASON_PING);
pingCount.incrementAndGet(); // 计数+1
try {
thread.schedulePing(pongCallback); // 发送ping指令
} catch (RemoteException ignored) {
// 进程已死亡时模拟响应
pongCallback.sendResult(null);
}
}
}
}
}
}
/* 阶段2:发送哨兵pong并等待响应 */
pongCallback.sendResult(null); // 触发最终检查
// 最多等待30秒(每秒打印进度)
for (int i = 0; i < 30; i++) {
try {
if (finishedLatch.await(1, TimeUnit.SECONDS)) {
pw.println("Finished application barriers!");
return; // 所有进程响应完成
} else {
pw.println("Waiting... " + pongCount.get() + "/" + pingCount.get());
}
} catch (InterruptedException ignored) {
}
}
pw.println("Gave up waiting!"); // 超时放弃
}
2.3 Binder冻结不成功或冻结后仍有Binder事务,则触发解冻
Binder冻结不成功或冻结后仍有Binder事务,则触发解冻
->CachedAppOptimizer.freezeProcess
->->CachedAppOptimizer.handleBinderFreezerFailure(final ProcessRecord proc, final String reason)
->->->CachedAppOptimizer.unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force)
/**
* 冻结目标进程的核心方法
*
* 关键流程:
* 1. 检查进程状态是否允许冻结
* 2. 冻结Binder通信通道
* 3. 通过cgroup freezer冻结进程
* 4. 更新进程和UID的冻结状态
*/
@GuardedBy({"mAm"})
private void freezeProcess(final ProcessRecord proc) {
// 初始信息获取(不持锁)
int pid = proc.getPid();
final String name = proc.processName;
final ProcessCachedOptimizerRecord opt = proc.mOptRecord;
synchronized (mProcLock) {
/* --- 解冻条件检查(提前终止冻结的场景)--- */
// 1. 检查冻结请求是否已被取消
if (!opt.isPendingFreeze()) {
return;
}
opt.setPendingFreeze(false); // 清除待处理标记
// 2. 全局冻结功能被覆盖(调试用途)
if (mFreezerOverride) {
opt.setFreezerOverride(true);
Slog.d(TAG_AM, "Skipping freeze for process " + pid + "(override)");
return;
}
// 3. 进程标记为不应冻结(如持有唤醒锁)
if (opt.shouldNotFreeze()) {
if (DEBUG_FREEZER) Slog.d(TAG_AM, "Skip freeze: process marked shouldNotFreeze");
return;
}
// 4. 进程已冻结或正在退出
if (pid == 0 || opt.isFrozen()) {
if (DEBUG_FREEZER) Slog.d(TAG_AM, "Skip freeze: already frozen or dying");
return;
}
/* --- 执行冻结流程 --- */
Slog.d(TAG_AM, "freezing " + pid + " " + name);
// 阶段1:冻结Binder通信(防止事务丢失)
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,"freezeBinder:" + name);
if (freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS) != 0) {
// Binder事务未完成时触发解冻(解冻条件1)
handleBinderFreezerFailure(proc, "outstanding txns");
return;
}
} catch (RuntimeException e) {
// Binder冻结失败时杀死进程(解冻条件2)
mFreezeHandler.post(() -> killProcessForFreezeFailure(proc));
return;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
// 阶段2:cgroup freezer冻结
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,"setProcessFrozen:" + name);
Process.setProcessFrozen(pid, proc.uid, true); // 写入cgroup.freeze=1
opt.setFrozen(true); // 更新内存状态
mFrozenProcesses.put(pid, proc); // 加入全局冻结列表
// 解冻条件3:UID下所有进程冻结时标记UID冻结
if (proc.getUidRecord().areAllProcessesFrozen()) {
proc.getUidRecord().setFrozen(true);
}
} catch (Exception e) {
Slog.w(TAG_AM, "Freeze failed for " + pid); // 解冻条件4:内核操作异常
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
// 阶段3:冻结后检查(防竞态条件)
if (opt.isFrozen()) {
try {
// 解冻条件5:冻结后仍有Binder事务(需解冻处理)
if ((getBinderFreezeInfo(pid) & TXNS_PENDING_WHILE_FROZEN) != 0) {
synchronized (mProcLock) {
handleBinderFreezerFailure(proc, "new pending txns");
}
}
} catch (RuntimeException e) {
// 解冻条件6:状态检查异常时解冻
mFreezeHandler.post(() -> killProcessForFreezeFailure(proc));
}
}
}
2.4 冻结进程持有文件锁并阻塞其他进程时,强制解冻该进程
冻结进程持有文件锁并阻塞其他进程时,强制解冻该进程
->CachedAppOptimizer.onBlockingFileLock(IntArray pids)
->->CachedAppOptimizer.unfreezeAppLSP
->->->CachedAppOptimizer.unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force)
/**
* 处理进程因文件锁阻塞的回调(由内核触发)
*
* 核心逻辑:当冻结进程持有文件锁并阻塞其他进程时,强制解冻该进程
*
* @param pids 包含两组PID的数组:
* - pids[0]: 持有文件锁的冻结进程PID
* - pids[1..N]: 被该锁阻塞的进程PID列表
*/
@GuardedBy({"mAm"})
@Override
public void onBlockingFileLock(IntArray pids) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "Blocking file lock found: " + pids);
}
// 双重锁保护(AMS全局锁 + 进程锁)
synchronized (mAm) {
synchronized (mProcLock) {
int pid = pids.get(0); // 获取持有锁的冻结进程PID
ProcessRecord app = mFrozenProcesses.get(pid); // 从冻结进程表中查找
if (app != null) {
/* 遍历所有被阻塞的进程,检查是否需要解冻:
* 1. 被阻塞进程必须是高优先级(adj < FREEZER_CUTOFF_ADJ)
* 2. 至少存在一个符合条件的被阻塞进程时触发解冻
*/
for (int i = 1; i < pids.size(); i++) {
int blocked = pids.get(i);
ProcessRecord pr = mAm.mPidsSelfLocked.get(blocked); // 获取被阻塞进程记录
if (pr != null && pr.mState.getCurAdj() < ProcessList.FREEZER_CUTOFF_ADJ) {
Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
+ pr.processName + " (" + blocked + ")");
/* 关键解冻条件:
* 1. 冻结进程持有文件锁(内核检测到flock/fcntl锁)
* 2. 被阻塞进程是前台/可见进程(adj < 900)
* 3. 解冻原因标识:UNFREEZE_REASON_FILE_LOCKS
*/
unfreezeAppLSP(app, UNFREEZE_REASON_FILE_LOCKS);
break; // 解冻后立即终止检查
}
}
}
}
}
}
2.5 adb shell解冻命令场景
基础命令格式:adb shell am unfreeze[options]
:目标应用的包名(如 com.example.app)或进程ID(如 1234)
options]:可选参数(如 --user 0指定用户)
例如命令:
adb shell am unfreeze --pid 9432
adb shell am unfreeze com.tencent.mp
adb shell cat /dev/freezer/frozen/cgroup.procs | grep -E "[0-9]{4}" | xargs adb shell ps -A | grep -Ei "com.tencent.mp"
ActivityManagerShellCommand.runFreeze("Unfreezing") -> CachedAppOptimizer.unfreezeAppInternalLSP
/**
* 执行进程冻结/解冻操作的命令行接口实现
*
* @param pw 输出流,用于打印操作结果
* @param freeze 操作类型:true=冻结,false=解冻
* @return 执行状态:0=成功,-1=失败
* @throws RemoteException 远程调用异常
*
* 关键流程说明:
* 1. 解析命令行参数(如--sticky标记)
* 2. 获取目标进程记录(ProcessRecord)
* 3. 执行冻结/解冻操作并输出结果
*/
@NeverCompile // 标记该方法不参与编译时优化
int runFreeze(PrintWriter pw, boolean freeze) throws RemoteException {
// 解析命令行附加参数(如--sticky持久化标记)
String freezerOpt = getNextOption();
boolean isSticky = false;
if (freezerOpt != null) {
isSticky = freezerOpt.equals("--sticky");
}
// 通过shell命令获取目标进程信息
ProcessRecord proc = getProcessFromShell();
if (proc == null) {
return -1; // 进程不存在或权限不足
}
// 打印操作日志(包含进程名、PID和sticky状态)
pw.print(freeze ? "Freezing" : "Unfreezing");
pw.print(" process " + proc.processName);
pw.println(" (" + proc.mPid + ") sticky=" + isSticky);
/* 核心冻结/解冻逻辑(需双重锁保护)
* 同步块说明:
* 1. mInternal锁:保证AMS服务状态一致性
* 2. mProcLock锁:防止进程状态并发修改
*/
synchronized (mInternal) {
synchronized (mInternal.mProcLock) {
// 设置持久化标记(控制解冻后是否保持低优先级)
proc.mOptRecord.setFreezeSticky(isSticky);
if (freeze) {
/* 强制异步冻结
* 内部流程:
* 1. 延迟10ms执行(避免锁竞争)
* 2. 调用Binder驱动冻结IPC通信
* 3. 写入cgroup.freeze=1触发内核冻结
*/
mInternal.mOomAdjuster.mCachedAppOptimizer.forceFreezeAppAsyncLSP(proc);
} else {
/* 内部解冻
* 参数说明:
* @param proc 目标进程
* @param reason 解冻原因(0=手动触发)
* @param immediate 是否跳过延迟立即执行
*/
mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(proc, 0, true);
}
}
}
return 0; // 操作成功
}
小结:
1.进程的冻结是通过cgroup的Freezer子系统来实现的,最终的原理是调度进程,不去申请cpu资源。
2.冻结的进程不会释放内存,因为当解冻时,要快速恢复,而无需重新加载或启动
3.冻结机制是针对cache进程
4.正常当进程变为非cache进程时候,就会解冻
5.Android 13以后针对binder 调用进行了优化,对进程的binder先进行冻结,如果向冻住的进程发送同步binder请求会立刻收到错误码BR_FROZEN_REPLY
6.进程进行解冻的时候,会去检查,如果在冻结期间有收到同步binder的请求,就会kill掉这个进程
7.拥有INSTALL_PACKAGES权限的应用,能够豁免被冻结