Recent Tasks 管理:最近任务列表的维护、清除、锁定机制

最近任务列表,说白了就是咱们按那个「方块键」看到的界面。这个功能看似简单,但坑特别多。我当年刚接手系统UI时,就被各种奇怪的场景折磨过——应用明明退出了,列表里还挂着;用户想锁定某个任务,结果重启手机就丢了。今天咱们就把这块彻底讲透。

Recent Tasks 的数据结构

先看看系统怎么存这些任务。核心数据结构是 RecentTasks 类,它维护了一个有序列表:

// frameworks/base/services/core/java/com/android/server/am/RecentTasks.java
class RecentTasks {
    // 核心列表,按最近使用排序
    private final ArrayList<TaskRecord> mTasks = new ArrayList<>();
    
    // 最大数量,不同设备不一样
    private int mMaxRecentTasks = 48;  // 手机默认
    
    // 锁定的任务集合
    private final SparseArray<TaskRecord> mLockedTasks = new SparseArray<>();
}

这里有个细节:mTasks 里的顺序就是用户看到的顺序。最新的任务插在头部,最旧的挤到尾部。当数量超过 mMaxRecentTasks 时,尾部任务会被移除。

核心要点:RecentTasks 不是简单的 FIFO 队列。它要考虑任务分组、锁定状态、Activity 类型等多种因素。我见过不少第三方 ROM 改这块改出 bug,就是因为只考虑了「先进先出」。

任务列表的维护机制

任务什么时候加入列表?什么时候移除?咱们一步步看。

加入时机

系统在 ActivityManagerServicestartActivity 流程中,会调用 addRecentTaskLocked

// 简化后的逻辑
void addRecentTaskLocked(TaskRecord task) {
    // 1. 检查是否应该加入
    if (!shouldAddToRecentTasks(task)) {
        return;
    }
    
    // 2. 移除旧的相同任务
    removeTaskByIntent(task.intent, task.userId);
    
    // 3. 插入到头部
    mTasks.add(0, task);
    
    // 4. 裁剪超出部分
    trimRecentTasks();
}

嗯,这里要注意 shouldAddToRecentTasks 的判断逻辑。我记得有一次排查问题,发现某个应用怎么都不出现在最近任务里。查了半天,原来是它的 Activity 设置了 excludeFromRecents=true

<activity
    android:name=".SecretActivity"
    android:excludeFromRecents="true" />

这个属性一设,系统直接跳过,根本不会加入列表。

移除时机

任务移除有几种情况:

  • 用户手动清除:滑动删除或点「清除全部」
  • 数量超限:最旧的任务被挤出
  • 应用被卸载:所有相关任务全部移除
  • 系统内存不足:低内存杀进程时,任务记录也会清理

我曾经遇到一个线上问题:用户反馈「清除全部」后,某些应用还在列表里。后来发现是 removeAllTasks 方法里忘了处理锁定任务:

void removeAllTasks() {
    for (int i = mTasks.size() - 1; i >= 0; i--) {
        TaskRecord task = mTasks.get(i);
        // 坑:锁定任务不应该被清除
        if (task.isLocked()) {
            continue;  // 跳过锁定任务
        }
        removeTaskAtIndex(i);
    }
}

注意:Android 10 之后,「清除全部」的行为变了。它不会清除锁定任务,也不会清除前台服务的任务。这个改动让很多用户困惑,但其实是合理的——你正在听音乐,不小心点了清除全部,音乐不应该停。

任务锁定机制

锁定功能是 Android 9 引入的。用户可以在最近任务列表里,点击任务卡片上的锁图标,把它锁定。

锁定的本质

锁定其实就是在 TaskRecord 上设置一个标志位:

// frameworks/base/services/core/java/com/android/server/am/TaskRecord.java
class TaskRecord {
    boolean mIsLocked;  // 锁定标志
    
    void setLocked(boolean locked) {
        mIsLocked = locked;
        // 持久化到磁盘
        persistLockedState();
    }
}

这个状态会持久化到 task_persistent_data.xml 文件中。所以重启手机后,锁定状态还在。

锁定后的行为变化

锁定任务有几个特殊行为:

操作 未锁定 已锁定
滑动清除 移除任务 无反应(或提示已锁定)
清除全部 移除所有 保留锁定任务
数量超限裁剪 移除最旧 跳过锁定任务
应用卸载 移除相关任务 移除(锁定不防卸载)

你想想看,锁定其实只防「误清除」,不防「应用被卸载」。这个设计很合理——你锁定了微信,但你把微信卸载了,任务留着也没意义。

核心流程图

下面这张图展示了最近任务从创建到销毁的完整生命周期:

Recent Tasks 生命周期流程图 启动 Activity 是否加入? 加入 Recent Tasks 用户滑动清除 用户点击锁定 数量超限裁剪 移除任务 设置锁定标志 移除最旧未锁定 启动流程 判断节点 正常流程 移除流程

清除策略的演进

不同 Android 版本的清除策略不太一样。我整理了一下:

版本 清除全部行为 锁定支持
Android 5.x 清除所有任务,包括前台服务 不支持
Android 6-8 清除所有,但保留前台服务 不支持
Android 9 清除所有,保留前台服务和锁定任务 支持
Android 10+ 同上,但增加了更多豁免条件 支持,持久化到磁盘

为什么会这样演进?说白了就是用户体验的不断优化。早期版本「一刀切」的做法,经常导致音乐播放器、下载任务被意外中断。后来 Google 学聪明了,加了各种豁免条件。

避坑指南:如果你在开发系统应用,需要监听「清除全部」事件,别用 onTaskRemoved。这个回调在普通清除时能触发,但「清除全部」时可能不会。我曾经被这个坑过,后来改用 UsageStatsManager 的监听才搞定。

锁定机制的实现细节

锁定功能在 UI 层和 Framework 层都有实现。UI 层负责显示锁图标和处理点击事件,Framework 层负责存储状态和执行策略。

持久化存储

锁定状态存在 /data/system/task_persistent_data.xml 里:

<task-persistent-data>
    <task id="42" locked="true" />
    <task id="56" locked="false" />
    <task id="78" locked="true" />
</task-persistent-data>

系统启动时,RecentTasks 会读取这个文件,恢复锁定状态。嗯,这里有个细节:如果某个任务对应的应用被卸载了,这个记录就成了「孤儿」。系统在下次启动时会自动清理这些无效记录。

锁定与分屏的交互

锁定任务在分屏模式下有个特殊行为:如果两个任务中有一个被锁定,分屏退出时,锁定的任务会保留,另一个会被清除。这个逻辑在 ActivityManagerServiceremoveStacksInWindowingModes 方法里:

void removeStacksInWindowingModes(int windowingMode) {
    for (int i = mStacks.size() - 1; i >= 0; i--) {
        ActivityStack stack = mStacks.get(i);
        if (stack.getWindowingMode() == windowingMode) {
            TaskRecord topTask = stack.getTopTask();
            // 锁定任务不参与自动清除
            if (topTask != null && topTask.isLocked()) {
                continue;
            }
            stack.finishAllActivities();
        }
    }
}

我记得有一次测试分屏功能,发现退出分屏后两个应用都消失了。查了半天,原来是锁定标志没传对——UI 层点了锁定,但 Framework 层没收到回调。这种跨进程通信的 bug 最难排查。

性能优化建议

最近任务列表如果维护不当,会影响系统性能。我给出几个建议:

  • 控制列表大小mMaxRecentTasks 不要设太大。手机 48 个够用,平板可以适当增加到 64 个。
  • 延迟加载缩略图:最近任务的截图不要一次性加载完。用户滑动时再按需加载,能省不少内存。
  • 批量操作加锁:清除全部时,用 batchUpdate 模式,避免频繁通知 UI 刷新。

总结一下:Recent Tasks 管理看似简单,但涉及任务生命周期、用户交互、持久化存储等多个方面。锁定机制是 Android 9 之后的重要改进,它给了用户更多控制权。理解这些细节,能帮你写出更稳定的系统应用。


公众号:蓝海资料掘金营,微信deep3321