基于版本:Android R
0. 前言
在之前的两篇博文《Android 中app内存回收优化(一)》和 《Android 中app内存回收优化(二)》中详细剖析了 Android 中 app 内存优化的流程。这个机制的管理通过 CachedAppOptimizer 类管理,为什么叫这个名字,而不叫 AppCompact 等?在之前的两篇博文中也提到了,因为该类中还管理了一个重要功能:freezer,一个针对应用进程长期处于 Cached 状态的优化。
本文将继续分析 CachedAppOptimizer 类另一个功能 freezer。
1. Freezer 触发
在《Android oom_adj 更新原理(二)》中详细剖析了 OomAdjuster.applyOomAdjLocked() 函数,在 oom_adj 发生变化之后会重新 compute 然后在 apply, 在该函数中就是通过调用 updateAppFreezeStateLocked(app) 来确认是否冻结进程。
1.1 updateAppFreezeStateLocked()
frameworks/base/services/core/java/com/android/server/am/OomAdjuster.javavoid updateAppFreezeStateLocked(ProcessRecord app) {// 确定该功能是使能的if (!mCachedAppOptimizer.useFreezer()) {return;}// 如果该进程处于 frozen状态,但sholudNoFreeze变为true,需要解冻if (app.frozen && app.shouldNotFreeze) {mCachedAppOptimizer.unfreezeAppLocked(app);}// 如果该进程的 adj处于 CACHED,并且可以冻结,则调用 freezeAppAsync() 冻结// 如果该进程的 adj离开 CACHED,则解冻if (app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ && !app.frozen && !app.shouldNotFreeze) {mCachedAppOptimizer.freezeAppAsync(app);} else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && app.frozen) {mCachedAppOptimizer.unfreezeAppLocked(app);}}
2. CachedAppOptimizer.init()
对于CachedAppOptimizer 的构造调用,以及 init() 函数的触发流程,可以参考《Android 中app内存回收优化(一)》 一文第 1 节 和 第 2 节。
frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.javapublic void init() {...synchronized (mPhenotypeFlagLock) {...updateUseFreezer();}}
2.1 updateUseFreezer()
private void updateUseFreezer() {// 获取settings 中属性 cached_apps_freezer 的值,根据属性值初始化变量mUseFreezerfinal 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();}if (mUseFreezer && mFreezeHandler == null) {Slog.d(TAG_AM, "Freezer enabled");enableFreezer(true);if (!mCachedAppOptimizerThread.isAlive()) {mCachedAppOptimizerThread.start();}mFreezeHandler = new FreezeHandler();Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),Process.THREAD_GROUP_SYSTEM);} else {enableFreezer(false);}}
首先确认 freezer 功能是否使能,用个流程图来说明比较清晰:
当 freezer 使能,就会:
- 调用 enableFreezer() 进行使能,详细的流程可以查看第 4 节;
- 如果 CachedAppOptimizer 中的 ServiceThread 没有启动,则启动;
- 创建 free handler,用以处理 freezer 相关消息;
- 设置 ServiceThread 的优先级为 THREAD_GROUP_SYSTEM;
3. cgroups 简介
cgroups (全称:control groups) 是 Linux 内核提供的一种可以限制单个进程或者多个进程所使用资源的机制,可以对 CPU、memory 等资源实现精细化的控制。目前越来越活的轻量级容器 Docker 就使用了 cgroups 提供的资源限制能力来完成 CPU、memory 等部门的资源控制。
cgroups 为每种可以控制的资源定义了一个子系统,典型的子系统如下:
- cpu:主要限制进程的 cpu 使用率;
- cpuaat:可以统计 cgroups 中的进程的 cpu 使用报告;
- cpuset:可以为 cgroups 中的进程分配单独的 cpu 节点或内存节点;
- memory:可以限制进程的 memory 使用量;
- blkio:可以限制进程的块设备 io;
- devices:可以控制进程能够访问某些设备;
- freezer:可以挂起或恢复 cgroups 中的进程;
- net_cls:可以标记 cgroups 中进程的网络数据包,然后可以使用 tc (traffic control)模块对数据包进行控制;
- ns:可以使不同的 cgroups 下面的进程使用不同的 namespace;
在 Android Q 或更高版本通过 task profiles 使用 cgroup 抽象层,task profiles 可以用来描述应用于某个线程或进程的一个set 或 sets 的限制。系统依照 task profiles 的规定选择一个或多个适当的 cgroups。通过这种限制,可以对底层的 cgroup 功能集进行更改,而不会影响较高的软件层。
具体的细节可以查看博文:《Android 中 cgroup抽象层详解》
本文的很多重要特性都是通过 profile 的方式完成。
4. enableFreezer()
主要是调用 enableFreezerInternal() 函数:
private static native void enableFreezerInternal(boolean enable);
frameworks/base/services/core/jni/com_android_server_am_CachedAppOptimizer.cppstatic void com_android_server_am_CachedAppOptimizer_enableFreezerInternal(JNIEnv *env, jobject clazz, jboolean enable) {bool success = true;if (enable) {success = SetTaskProfiles(0, {"FreezerEnabled"}, true);} else {success = SetTaskProfiles(0, {"FreezerDisabled"}, true);}if (!success) {jniThrowException(env, "java/lang/RuntimeException", "Unknown error");}
}
通过接口 SetTaskProfiles() 往对应的节点写入特定的 value 值,详细可以查看博文:《Android 中 cgroup抽象层详解》
5. freezeAppAsync()
参数为 ProcessRecord 类型,也就是对应的进程。
void freezeAppAsync(ProcessRecord app) {mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);mFreezeHandler.sendMessageDelayed(mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),FREEZE_TIMEOUT_MS);}
注意,
- 如果该进程已经发送 freeze 请求,再次发送请求时,先取消原来的消息;
- 发送消息 SET_FROZEN_PROCESS_MSG,请求 freeze;
- 消息处理的延时时长为 FREEZE_TIMEOUT_MS(10 min),如果10 分钟之后,冻结该进程的消息还没有被取消,则进入冻结进程的流程;
6. unfreezeAppLocked()
void unfreezeAppLocked(ProcessRecord app) {// 首先,取消之前该进程的冻结请求mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);// 如果进程还没有冻结,则无需做解冻处理if (!app.frozen) {return;}/********进程处于冻结,进行解冻处理*********/boolean processKilled = false;// 冻住的进程可以接收异步binder请求,但是不会处理,只是放入binder buffer, 过多的请求会导致buffer耗尽;// 这里需要确认下该进程在解冻之前,进程是否在冰冻期间收到同步的binder 请求,有则kill该进程try {int freezeInfo = getBinderFreezeInfo(app.pid);if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "+ " received sync transactions while frozen, killing");app.kill("Sync transaction while in frozen state",ApplicationExitInfo.REASON_OTHER,ApplicationExitInfo.SUBREASON_INVALID_STATE, true);processKilled = true;}if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "+ " received async transactions while frozen");}} catch (Exception e) {Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + app.pid + " "+ app.processName + ". Killing it. Exception: " + e);app.kill("Unable to query binder frozen stats",ApplicationExitInfo.REASON_OTHER,ApplicationExitInfo.SUBREASON_INVALID_STATE, true);processKilled = true;}if (processKilled) {return;}long freezeTime = app.freezeUnfreezeTime;try {freezeBinder(app.pid, false);} catch (RuntimeException e) {Slog.e(TAG_AM, "Unable to unfreeze binder for " + app.pid + " " + app.processName+ ". Killing it");app.kill("Unable to unfreeze",ApplicationExitInfo.REASON_OTHER,ApplicationExitInfo.SUBREASON_INVALID_STATE, true);return;}try {Process.setProcessFrozen(app.pid, app.uid, false);app.freezeUnfreezeTime = SystemClock.uptimeMillis();app.frozen = false;} catch (Exception e) {Slog.e(TAG_AM, "Unable to unfreeze " + app.pid + " " + app.processName+ ". This might cause inconsistency or UI hangs.");}if (!app.frozen) {if (DEBUG_FREEZER) {Slog.d(TAG_AM, "sync unfroze " + app.pid + " " + app.processName);}mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,app.pid,(int) Math.min(app.freezeUnfreezeTime - freezeTime, Integer.MAX_VALUE),app.processName));}}