摘要:客户认为重启设备后点击recent虚拟按键出现的任务快照代表程序正在运行,所以需要清除来表示重启后没有启动过任何应用。本文首先明确任务快照的定义,同时给出正在运行和没有运行的解释说明,最后给出方案。
一、任务快照
Android 系统的最近任务(Recent Apps)列表中的图标只是历史访问记录,不等同于正在后台运行的进程。
设备重启后,在最近任务界面依然能看到您应用的图标,这是系统保存的历史任务快照,并不是 App 还在后台存活、占用内存 / CPU。
原理:Android 会把用户之前打开过的 Activity 任务记录保存在系统任务栈快照里,用于快速恢复页面。这个任务记录是静态元数据 + 页面缩略图 ,独立于 App 进程。
当设备重启后,所有 App 进程都会被系统全部销毁;但部分 Android 版本 / 定制 ROM 会保留历史任务列表记录,重启后依然展示。点击图标才会触发 App 重新启动,不点击则 App 进程不存在,不会后台运行、耗电。
二、原理及说明
1. Recent Task 本质区分两个概念
- 任务记录(Task Record) :系统维护的任务快照,包含应用包名、图标、页面缩略图,只是一条记录,不是进程。
- App 进程(Process):App 真正运行时,在 Linux 内核创建的进程,占用内存、CPU。
✅ 重启手机动作:杀死所有 App 进程 ;但不一定清除系统里保存的 Task 记录 (取决于 ROM 厂商配置、Android 版本)。
所以重启后列表还能看到图标 = 任务快照还在,进程已经不存在。
2. 点击和不点击的行为差异
- 不点击:App 进程不存在,不会执行任何代码、不耗电、不联网。
- 点击该图标:系统根据这个历史任务记录,重新拉起 App 进程,恢复上次页面。
3. 为什么重启后还保留任务列表(Android 原生 + 厂商 ROM 差异)
- 原生 Android:一般重启会清空 Recent 任务;
- 很多厂商定制 ROM(MTK、高通平台)做了优化:持久化最近任务列表,重启后恢复历史任务快照,提升用户体验,属于 ROM 定制行为,并非 App 驻留后台。
👉 重点:这个行为是系统 / ROM 层控制,不是 App 代码主动保活造成。App 本身没有能力在设备重启之后继续保持进程运行。
4. 举证方法
可以演示验证:
- 重启设备,打开最近任务,看到 App 图标;
- 使用
adb shell ps | grep [包名]查看进程:找不到 App 进程; - 点击最近任务里的 App 图标,再次执行 adb 命令,此时才能看到 App 进程启动。
三、清除方案
java
Index: frameworks/base/services/core/java/com/android/server/wm/RecentTasks.java
===================================================================
--- frameworks/base/services/core/java/com/android/server/wm/RecentTasks.java (revision 4780)
+++ frameworks/base/services/core/java/com/android/server/wm/RecentTasks.java (revision 4781)
@@ -491,6 +491,10 @@
List<Task> tasks = mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks);
mTasks.addAll(tasks);
cleanupLocked(userId);
+
+ // for clear historical tasks as needed
+ clearBootLoadedRecentsIfNeededLocked(userId);
+
mUsersWithRecentsLoaded.put(userId, true);
// If we have tasks added before loading recents, we need to update persistent task IDs.
@@ -499,6 +503,53 @@
}
}
+ /**
+ * Only called when loading the history recent task from the disk for the first time after booting up;
+ * Clear the legacy tasks from the previous shutdown, and the newly generated tasks after this shutdown will not be affected;
+ * persist.system.recent.clear_on_boot - Control the main switch;
+ * persist.system.recent.clear_pkg - Specify the package name to be cleared; If left blank, clear all app history tasks.
+ */
+ private void clearBootLoadedRecentsIfNeededLocked(int userId) {
+ final boolean enableClear = SystemProperties.getBoolean("persist.system.recent.clear_on_boot", true);
+ if (!enableClear) {
+ Slog.i(TAG, "clearBootLoadedRecents: feature disabled by prop");
+ return;
+ }
+ final String targetClearPkg = SystemProperties.get("persist.system.recent.clear_pkg", "");
+ Slog.i(TAG, "clearBootLoadedRecentsIfNeededLocked userId=" + userId + ", targetPkg=[" + targetClearPkg + "]");
+
+ // Reverse traversal and deletion to avoid List index confusion
+ for (int i = mTasks.size() - 1; i >= 0; i--) {
+ final Task task = mTasks.get(i);
+ if (task.mUserId != userId) {
+ continue;
+ }
+ String taskPkg = null;
+ if (task.realActivity != null) {
+ taskPkg = task.realActivity.getPackageName();
+ } else if (task.getBaseIntent() != null && task.getBaseIntent().getComponent() != null) {
+ taskPkg = task.getBaseIntent().getComponent().getPackageName();
+ }
+
+ boolean needRemove;
+ if (TextUtils.isEmpty(targetClearPkg)) {
+ // Prop does not set package name: Clear all historical tasks recovered from disk this time
+ needRemove = true;
+ } else {
+ // Only clear the specified package name
+ needRemove = targetClearPkg.equals(taskPkg);
+ }
+
+ if (needRemove) {
+ Slog.i(TAG, "clearBootLoadedRecents remove boot-restored taskId=" + task.mTaskId + " pkg=" + taskPkg);
+ // 1.Remove the task from the recent list and notify WMS to delete the corresponding task snapshot thumbnail file
+ mSupervisor.removeTask(task, false /*killProcess*/, REMOVE_FROM_RECENTS, "clear-boot-history");
+ // 2.Notify persister not to persist this cleared task again
+ notifyTaskPersisterLocked(task, false);
+ }
+ }
+ }
+
private void loadPersistedTaskIdsForUserLocked(int userId) {
// An empty instead of a null set here means that no persistent taskIds were present
// on file when we loaded them.