基于版本:Android S
0. 前言
在之前的两篇博文《Android 中app内存回收优化(一)》和 《Android 中app内存回收优化(二)》中详细剖析了 Android 中 app 内存优化的流程。这个机制的管理通过 CachedAppOptimizer 类管理,为什么叫这个名字,而不叫 AppCompact 等?在之前的两篇博文中也提到了,因为该类中还管理了一个重要功能:freezer,一个针对应用进程长期处于 Cached 状态的优化。
在之前博文《app freezer 原理 R 版本》中简单的剖析了app freeze / unfreeze 的流程,但从代码逻辑上来看,笔者觉得 R 版本上有些逻辑是存在问题的,好在S 版本中都修复了。
本文将以 R 版本的原理为基础,对 S 版本进行对比分析。
1. freezer 触发
同 R 版本中,S 版本的触发也是在 applyOomAdjLSP() 函数中调用 updateAppFreezeStateLSP() 来确认是否冻结进程:
frameworks/base/services/core/java/com/android/server/am/OomAdjuster.javaprivate void updateAppFreezeStateLSP(ProcessRecord app) {// 确认该功能是否使能,如果没有使能则返回if (!mCachedAppOptimizer.useFreezer()) {return;}// S 版本新加的,确认应用是否可以豁免if (app.mOptRecord.isFreezeExempt()) {return;}// S 版本中cached进程优化相关的属性,都放到了mOptRecord中管理final ProcessCachedOptimizerRecord opt = app.mOptRecord;// 如果进程处于frozen状态,但shouldNotFreeze变成true,需要解冻if (opt.isFrozen() && opt.shouldNotFreeze()) {mCachedAppOptimizer.unfreezeAppLSP(app);return;}// 确定adj,是进入freeze 还是 unfreeze 流程final ProcessStateRecord state = app.mState;if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ && !opt.isFrozen()&& !opt.shouldNotFreeze()) {mCachedAppOptimizer.freezeAppAsyncLSP(app);} else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ) {mCachedAppOptimizer.unfreezeAppLSP(app);}}
该函数与 R 版本基本逻辑差不多,稍微有些差异:
- 进程有了是否freeze 豁免的功能,当应用设置了 INSTALL_PACKAGES 的权限之后,该应用处于豁免状态,详细看 ProcessList.startProcessLocked() 函数;
- S 版本中cached进程的一些状态,统一由ProcessRecord 中的 mOptRecord 维护;
- 最后再unfreeze 调用时,不再判断app 是否处于 frozen,因为这部分逻辑判断会在 unfreeze 函数中判定,这让代码更简介,R 版本中有些多余;
2. CachedAppOptimizer.init()
对于CachedAppOptimizer 的构造调用,以及 init() 函数的触发流程,可以参考《Android 中app内存回收优化(二)》 一文第 1 节 和 第 2 节。
frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.javapublic void init() {...DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,ActivityThread.currentApplication().getMainExecutor(),mOnNativeBootFlagsChangedListener);mAm.mContext.getContentResolver().registerContentObserver(CACHED_APP_FREEZER_ENABLED_URI, false, mSettingsObserver);synchronized (mPhenotypeFlagLock) {...updateUseFreezer();...}}
相比较与 R 版本,这里多了两个功能:
- 新加一个 freeze_debounce_timeout 属性发生变化的 listener,当该属性变化时,会调用 updateFreezerDebounceTimeout() 进行更新;
- 新加了 cached_apps_freezer 属性值发生变化的 observer;
在 R 版本中,freeze timeout 是10min,使用的是常量。而在 S 版本中,将该值设计为可变的,用户可以通过 DeviceConfig 进行修改。当发生变化时,会调用 updateFreezerDebounceTimeout()进行更新。
另外,在 S 版本中,对 cached_apps_freezer 的值做了一个observer,及时控制 freezer 的使能。
2.1 updateUseFreezer()
private void updateUseFreezer() {// 获取 settings中属性 cached_apps_freezer的值,同R 版本final String configOverride = Settings.Global.getString(mAm.mContext.getContentResolver(),Settings.Global.CACHED_APPS_FREEZER_ENABLED);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();updateFreezerDebounceTimeout();} else {mUseFreezer = false;}final boolean useFreezer = mUseFreezer;// enableFreezer() would need the global ActivityManagerService lock, post it.mAm.mHandler.post(() -> {if (useFreezer) {Slog.d(TAG_AM, "Freezer enabled");enableFreezer(true);if (!mCachedAppOptimizerThread.isAlive()) {mCachedAppOptimizerThread.start();}if (mFreezeHandler == null) {mFreezeHandler = new FreezeHandler();}Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),mCompactionPriority);} else {Slog.d(TAG_AM, "Freezer disabled");enableFreezer(false);}});}
这里针对 R 版本也做了个优化,将enableFreezer() 的过程异步处理。
freezer 功能是否使能,用流程图说明比较清晰:
3. cgroups 简介
这里不再补充,详细可以查看 R 版本或《Android 中 cgroup抽象层详解》
这里需要注意的是,R 版本中的 freezer 中,通过 /sys/fs/cgroup/freezer/cgroup.freeze 来确定是否使能 freezer,通过 /sys/fs/cgroup/freezer/cgroup.procs 来进行 frozen / unfrozen 操作。而在 S 版本中,frozen/unfrozen 的操作通过 /sys/fs/cgroup/freezer/cgroup.freeze 节点。详细看下面第 5.2.1 节。
4. enableFreezer()
frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.javapublic synchronized boolean enableFreezer(boolean enable) {if (!mUseFreezer) {return false;}if (enable) {mFreezerDisableCount--;if (mFreezerDisableCount > 0) {return true;} else if (mFreezerDisableCount < 0) {Slog.e(TAG_AM, "unbalanced call to enableFreezer, ignoring");mFreezerDisableCount = 0;return false;}} else {mFreezerDisableCount++;if (mFreezerDisableCount > 1) {return true;}}// Override is applied immediately, restore is delayedsynchronized (mAm) {synchronized (mProcLock) {mFreezerOverride = !enable;Slog.d(TAG_AM, "freezer override set to " + mFreezerOverride);mAm.mProcessList.forEachLruProcessesLOSP(true, process -> {if (process == null) {return;}final ProcessCachedOptimizerRecord opt = process.mOptRecord;if (enable && opt.hasFreezerOverride()) {freezeAppAsyncLSP(process);opt.setFreezerOverride(false);}if (!enable && opt.isFrozen()) {unfreezeAppLSP(process);// Set freezerOverride *after* calling unfreezeAppLSP (it resets the flag)opt.setFreezerOverride(true);}});}}return true;}
代码与 R 版本不同,这里通过 mAm.mProcessList.forEachLruProcessesLOSP() 对每个LRU 中的进程进行确认。
进程中引入了 mOptRecord.mFreezerOverride 属性,用以标记对某个进程是否进行 disable freezer 操作。若该值为 true,则表示进程被 disable freezer 过。此时如果再次 enable,需对进程进行 freeze 请求。
当然,CachedAppOptimizer 类中也有这样的成员变量 mFreezerOverride,这个用以控制从外部调用的 freezeAppAsyncLSP()。如果该值为 true,则表示disable freezer了,外部如果有调用 freezeAppAsyncLSP(),则不需要去处理。
5. freezeAppAsyncLSP()
void freezeAppAsyncLSP(ProcessRecord app) {final ProcessCachedOptimizerRecord opt = app.mOptRecord;if (opt.isPendingFreeze()) {// Skip redundant DO_FREEZE messagereturn;}mFreezeHandler.sendMessageDelayed(mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),mFreezerDebounceTimeout);opt.setPendingFreeze(true);}
相比较 R 版本,这里做了一个保护,防止反复进行 freeze 请求。
另外,timeout 从 R 版本中的常量 FREEZE_TIMEOUT_MS 改成可变的 debounce timeout。
注意:冻结时异步操作,使用 CachedAppOptimizer类中定义的 ServiceThread 进行,而解冻是东部操作,没有通过 ServiceThread。
5.1 freeze 消息处理
public void handleMessage(Message msg) {switch (msg.what) {case SET_FROZEN_PROCESS_MSG:synchronized (mAm) {freezeProcess((ProcessRecord) msg.obj);}break;case REPORT_UNFREEZE_MSG:int pid = msg.arg1;int frozenDuration = msg.arg2;String processName = (String) msg.obj;reportUnfreeze(pid, frozenDuration, processName);break;default:return;}}
对于 freezer 一共有两个消息,REPORT_UNFREEZE_MSG 这个消息是在 unfreeze 之后进行记录的。
本文重点来看下 freeze 的消息处理,这里看到最终调用的是 freezeProcess() 函数,详细查看下一小节
5.2 freezeProcess()
frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.javaprivate void freezeProcess(final ProcessRecord proc) {int pid = proc.getPid(); // Unlocked intentionallyfinal String name = proc.processName;final long unfrozenDuration;final boolean frozen;final ProcessCachedOptimizerRecord opt = proc.mOptRecord;opt.setPendingFreeze(false);try {// 确认进程是否存在任意的文件锁,避免不必要的 free/unfreeze操作// 这是为了防止冻结进程持有文件锁,从而引起死锁// 冻结成功之后,还会再次确认文件锁,如果有锁,则立即解冻if (mProcLocksReader.hasFileLocks(pid)) {if (DEBUG_FREEZER) {Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, not freezing");}return;}} catch (Exception e) {Slog.e(TAG_AM, "Not freezing. Unable to check file locks for " + name + "(" + pid+ "): " + e);return;}synchronized (mProcLock) {pid = proc.getPid();// 如果进程没有变成Cached或者不能freeze,则退出此次freeze操作if (proc.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ|| opt.shouldNotFreeze()) {return;}// 如果freezer 处于disable状态,返回,并告知该进程if (mFreezerOverride) {opt.setFreezerOverride(true);return;}// 已经处于frozen,或者不是一个应用进程,则退出此次 freeze操作// pid为0,有可能进程还没有launch完成,或者进程被kill了if (pid == 0 || opt.isFrozen()) {// Already frozen or not a real process, either one being// launched or one being killedreturn;}Slog.d(TAG_AM, "freezing " + pid + " " + name);// 在S版本中,将这部分功能提前,也是正确的行为// 冻结 binder// 1.如果freezer是使能,将同步发送所有的pending 交互给指定的pid;// 2.该函数调用后,所有的binder 请求,都会被block,并返回error给发送请求的进程;try {if (freezeBinder(pid, true) != 0) {rescheduleFreeze(proc, "outstanding txns");return;}} catch (RuntimeException e) { //如果调用失败,则直接kill该进程Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);mFreezeHandler.post(() -> {synchronized (mAm) {proc.killLocked("Unable to freeze binder interface",ApplicationExitInfo.REASON_OTHER,ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);}});}long unfreezeTime = opt.getFreezeUnfreezeTime();//核心函数 setProcessFrozen(),同步冻结进程try {Process.setProcessFrozen(pid, proc.uid, true);opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());opt.setFrozen(true);} catch (Exception e) {Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);}unfrozenDuration = opt.getFreezeUnfreezeTime() - unfreezeTime;frozen = opt.isFrozen();}if (!frozen) {return;}Slog.d(TAG_AM, "froze " + pid + " " + name);// 将此次 freeze 记录到 event log 中EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);// See above for why we're not taking mPhenotypeFlagLock hereif (mRandom.nextFloat() < mFreezerStatsdSampleRate) {FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,pid,name,unfrozenDuration);}// 确认在冻结的时候,是否收到了 TXNS_PENDING_WHILE_FROZEN的binder请求,// 如果有有收到请求,则重新freeze 该进程(unfreeze + freeze)try {// post-check to prevent racesint freezeInfo = getBinderFreezeInfo(pid);if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) {synchronized (mProcLock) {rescheduleFreeze(proc, "new pending txns");}return;}} catch (RuntimeException e) {Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);mFreezeHandler.post(() -> {synchronized (mAm) {proc.killLocked("Unable to freeze binder interface",ApplicationExitInfo.REASON_OTHER,ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);}});}try {// 再次check文件锁,如果该冻结进程持有文件锁,立即unfreezeif (mProcLocksReader.hasFileLocks(pid)) {if (DEBUG_FREEZER) {Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze");}unfreezeAppLSP(proc);}} catch (Exception e) {Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);unfreezeAppLSP(proc);}}
逻辑与R 版本有很大的不同:
- 在冻结进程之前,调用 freezeBinder(),用以冻结binder 通信;
- 在冻结进程之后,调用 getBinderFreezeInfo(),确认是否在冻结的时候有 TXNS_PENDING_WHILE_FROZEN 的binder 请求,如果有该请求,则重新freeze 该进程(unfreeze + freeze);
弄个流程图理解冻结过程:
5.2.1 setProcessFrozen()
frameworks/base/core/java/android/os/Process.javapublic static final native void setProcessFrozen(int pid, int uid, boolean frozen);
frameworks/base/core/jni/android_util_Process.cppvoid android_os_Process_setProcessFrozen(JNIEnv *env, jobject clazz, jint pid, jint uid, jboolean freeze)
{bool success = true;if (freeze) {success = SetProcessProfiles(uid, pid, {"Frozen"});} else {success = SetProcessProfiles(uid, pid, {"Unfrozen"});}if (!success) {signalExceptionForGroupError(env, EINVAL, pid);}
}
此处的调用在博文《cgroup抽象层》中已经分析过,通过接口 SetProcessProfiles() 精细是 SetAttributeAction 类型的profile,最终调用 ExecuteForProcess():
system/core/libprocesscgroup/task_profiles.cppbool SetAttributeAction::ExecuteForProcess(uid_t, pid_t pid) const {return ExecuteForTask(pid);
}bool SetAttributeAction::ExecuteForTask(int tid) const {std::string path;if (!attribute_->GetPathForTask(tid, &path)) {LOG(ERROR) << "Failed to find cgroup for tid " << tid;return false;}if (!WriteStringToFile(value_, path)) {PLOG(ERROR) << "Failed to write '" << value_ << "' to " << path;return false;}return true;
}
最终写的是 /sys/fs/cgroup/freezer/cgroup.freeze 文件。
某个进程被冻结的旅程图:
6. unfreezeAppLSP()
frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.javavoid unfreezeAppLSP(ProcessRecord app) {final int pid = app.getPid();final ProcessCachedOptimizerRecord opt = app.mOptRecord;// 进程已经处于peding freeze中,移除冻结消息if (opt.isPendingFreeze()) {// Remove pending DO_FREEZE messagemFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);opt.setPendingFreeze(false);}opt.setFreezerOverride(false);// 如果进程还没有冻结,则无需做解冻处理if (!opt.isFrozen()) {return;}// 冻住的进程可以接收异步binder请求,但是不会处理,只是放入binder buffer, 过多的请求会导致buffer耗尽;// 这里需要确认下该进程在解冻之前,进程是否在冰冻期间收到同步的binder 请求,有则kill该进程boolean processKilled = false;try {int freezeInfo = getBinderFreezeInfo(pid);if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {Slog.d(TAG_AM, "pid " + pid + " " + app.processName+ " received sync transactions while frozen, killing");app.killLocked("Sync transaction while in frozen state",ApplicationExitInfo.REASON_OTHER,ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION, true);processKilled = true;}if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0 && DEBUG_FREEZER) {Slog.d(TAG_AM, "pid " + pid + " " + app.processName+ " received async transactions while frozen");}} catch (Exception e) {Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + pid + " "+ app.processName + ". Killing it. Exception: " + e);app.killLocked("Unable to query binder frozen stats",ApplicationExitInfo.REASON_OTHER,ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);processKilled = true;}//进程被kill 了,无需 unfreezeif (processKilled) {return;}// app.freezeUnfreezeTime记录的是上次free、unfreeze的时间long freezeTime = opt.getFreezeUnfreezeTime();try {freezeBinder(pid, false);} catch (RuntimeException e) {Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName+ ". Killing it");app.killLocked("Unable to unfreeze",ApplicationExitInfo.REASON_OTHER,ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);return;}try {Process.setProcessFrozen(pid, app.uid, false);opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());opt.setFrozen(false);} catch (Exception e) {Slog.e(TAG_AM, "Unable to unfreeze " + pid + " " + app.processName+ ". This might cause inconsistency or UI hangs.");}if (!opt.isFrozen()) {Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName);mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,pid,(int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),app.processName));}}
逻辑比较清晰,核心处理函数是 setProcessFrozen(),详细的流程在上面第 5.2.1 节中已经分析过。
7. kernel 处理