当前位置: 首页 > news >正文

AOSP Android14 Launcher3——RecentsView最近任务数据加载

最近任务是Launcher中的一个重要的功能,显示用户最近使用的应用,并可以快速切换到其中的应用;用户可以通过底部上滑停顿进入最近任务,也可以在第三方应用底部上滑进最近任务。

这两种场景之前的博客也介绍过,本文就不再赘述。
本篇就来讲讲最近任务中的这些任务卡片是如何一步步加载并显示在页面上的。

RecentsView

这个类是最近任务的核心类。
RecentsView继承自PagedView,所以,这个页面可以上下或者左右滚动,展示最近任务卡片并快速滚动。
最近任务卡片即TaskView,是最近任务RecentsView的childView。RecentsView将TaskView通过addView的方式添加到界面上。如图所示。

在这里插入图片描述
RecentsView是如何将TaskView添加的呢?其中显示的每个Task的数据哪里来的呢?

这里就涉及到一个重要的方法reloadIfNeed

 // quickstep/src/com/android/quickstep/views/RecentsView.java/*** Reloads the view if anything in recents changed.*/public void reloadIfNeeded() {if (!mModel.isTaskListValid(mTaskListChangeId)) {mTaskListChangeId = mModel.getTasks(this::applyLoadPlan, RecentsFilterState.getFilter(mFilterState.getPackageNameToFilter()));}}

这个方法通过RecentsModel的方法getTasks()来获取任务列表。最后调用RecentTaskList的getTask方法

quickstep/src/com/android/quickstep/RecentTasksList.java/*** Asynchronously fetches the list of recent tasks, reusing cached list if available.** @param loadKeysOnly Whether to load other associated task data, or just the key* @param callback The callback to receive the list of recent tasks* @return The change id of the current task list*/public synchronized int getTasks(boolean loadKeysOnly,Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter) {final int requestLoadId = mChangeId;...... 省略// Kick off task loading in the backgroundmLoadingTasksInBackground = true;UI_HELPER_EXECUTOR.execute(() -> {if (!mResultsBg.isValidForRequest(requestLoadId, loadKeysOnly)) {// 异步加载任务mResultsBg = loadTasksInBackground(Integer.MAX_VALUE, requestLoadId, loadKeysOnly);}TaskLoadResult loadResult = mResultsBg;mMainThreadExecutor.execute(() -> {mLoadingTasksInBackground = false;mResultsUi = loadResult;if (callback != null) {// filter the tasks if needed before passing them into the callbackArrayList<GroupTask> result = mResultsUi.stream().filter(filter).map(GroupTask::copy).collect(Collectors.toCollection(ArrayList<GroupTask>::new));callback.accept(result);}});});return requestLoadId;}

其中的loadTasksInBackground是真正加载任务数据的地方,代码如下:

quickstep/src/com/android/quickstep/RecentTasksList.java/*** Loads and creates a list of all the recent tasks.*/@VisibleForTestingTaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) {int currentUserId = Process.myUserHandle().getIdentifier();// 获取最近任务数据ArrayList<GroupedRecentTaskInfo> rawTasks =mSysUiProxy.getRecentTasks(numTasks, currentUserId);// The raw tasks are given in most-recent to least-recent order, we need to reverse itCollections.reverse(rawTasks);SparseBooleanArray tmpLockedUsers = new SparseBooleanArray() {@Overridepublic boolean get(int key) {if (indexOfKey(key) < 0) {// Fill the cached locked state as we fetchput(key, mKeyguardManager.isDeviceLocked(key));}return super.get(key);}};TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());int numVisibleTasks = 0;...... 省略  数据加工处理return allTasks;}

接着又在SystemUiProxy的getRecentTasks方法中加载

quickstep/src/com/android/quickstep/SystemUiProxy.javapublic ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {if (mRecentTasks == null) {Log.w(TAG, "getRecentTasks() failed due to null mRecentTasks");return new ArrayList<>();}try {final GroupedRecentTaskInfo[] rawTasks = mRecentTasks.getRecentTasks(numTasks,RECENT_IGNORE_UNAVAILABLE, userId);if (rawTasks == null) {return new ArrayList<>();}return new ArrayList<>(Arrays.asList(rawTasks));} catch (RemoteException e) {Log.w(TAG, "Failed call getRecentTasks", e);return new ArrayList<>();}}

其中的关键代码是

quickstep/src/com/android/quickstep/SystemUiProxy.java
final GroupedRecentTaskInfo[] rawTasks = mRecentTasks.getRecentTasks(numTasks,RECENT_IGNORE_UNAVAILABLE, userId);

这里的mRecentTasks是private IRecentTasks mRecentTasks; IRecentTasks类型,这是wm提供的一个AIDL接口。所以,最终最近任务的数据是来自于wm。

核心流程概述:

Launcher 加载最近任务数据的流程是一个典型的分层异步带缓存的设计:

  1. UI 层 (RecentsView): 需要数据来展示,并发起请求。
  2. 模型层 (RecentsModel): 作为中间层,管理数据缓存(图标、缩略图)和数据源(任务列表),并处理数据更新和分发。
  3. 数据源层 (RecentTasksList): 负责直接与系统服务交互,获取原始的最近任务列表,并提供简单的缓存机制。

详细步骤:

  1. 触发加载 (Triggering Load) - RecentsView:

    • RecentsView 需要显示或刷新最近任务列表时(例如,进入概览状态 setOverviewStateEnabled(true) (line 1404),或者视图附加到窗口 onAttachedToWindow() (line 1080) 后调用 reloadIfNeeded() (line 2546)),它会检查当前数据是否需要更新。
    • reloadIfNeeded() 方法会调用 mModel.isTaskListValid(mTaskListChangeId) (line 2548) 来检查当前 RecentsView 持有的任务列表 ID 是否仍然有效。
    • 如果 ID 失效或从未加载过,它会调用 mModel.getTasks(this::applyLoadPlan, mFilterState) (line 2550) 来请求最新的任务数据。this::applyLoadPlan 是一个回调函数,当数据加载完成后会被执行。mFilterState 用于过滤任务。
  2. 请求传递 (Request Forwarding) - RecentsModel:

    • RecentsModelgetTasks(Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter) 方法 (line 151, 165) 接收到来自 RecentsView 的请求。
    • 不会自己直接去获取系统数据,而是将请求进一步委托RecentTasksList 实例 (mTaskList),调用 mTaskList.getTasks(false /* loadKeysOnly */, callback, filter) (line 167)。它将 RecentsView 传来的回调函数和过滤器原样传递下去。
  3. 检查缓存与异步加载 (Cache Check & Async Loading) - RecentTasksList:

    • RecentTasksList.getTasks(...) (line 121) 首先检查其UI 线程缓存 (mResultsUi) 是否包含针对当前请求 ID (mChangeId) 的有效数据(并且满足 loadKeysOnly 的要求)。mChangeId 会在系统任务列表发生变化时递增。
    • 缓存命中 (Cache Hit): 如果 mResultsUi 有效,它会立即(通过 mMainThreadExecutor.post) 将缓存的数据(经过 filter 过滤和拷贝 map(GroupTask::copy)) 传递给 RecentsViewapplyLoadPlan 回调。加载流程快速结束。
    • 缓存未命中 (Cache Miss): 如果 mResultsUi 无效,说明需要从系统重新加载。
      • 它会将 mLoadingTasksInBackground 标记为 true
      • 它使用 UI_HELPER_EXECUTOR (后台线程池) 调度一个后台任务来加载数据。
      • 后台任务首先检查后台线程缓存 (mResultsBg) 是否有效。如果无效,它会调用 loadTasksInBackground(...) (line 232)。
      • loadTasksInBackground(...) 调用 mSysUiProxy.getRecentTasks(...) (line 236) 通过 Binder (IPC) 从 SystemUI(或其他系统服务)获取原始的 GroupedRecentTaskInfo 列表。
      • 获取到原始数据后,loadTasksInBackground 进行处理:
        • 反转列表顺序(系统返回的是最新->最旧,PagedView 需要最旧->最新)。
        • 遍历 GroupedRecentTaskInfo
        • 为每个任务创建 Task.TaskKey
        • 如果 loadKeysOnlyfalse,则创建完整的 Task 对象 (Task.from(...)),包含任务的详细信息。
        • 处理单任务和分屏任务对,并将它们包装成 GroupTaskDesktopTask 对象。
        • 将处理后的 ArrayList<GroupTask> 存储在 mResultsBg 中。
      • 后台任务完成后,它会将结果 (mResultsBg) 通过 mMainThreadExecutor.execute 发送回主线程
  4. 数据返回与 UI 更新 (Data Return & UI Update) - RecentTasksList -> RecentsModel -> RecentsView:

    • 回到主线程后,RecentTasksList 将后台加载的结果 (mResultsBg) 复制到 UI 线程缓存 (mResultsUi) (line 160)。
    • mLoadingTasksInBackground 标记为 false
    • 调用最初由 RecentsView 传入的回调函数(即 applyLoadPlan),并将经过 filter 过滤和拷贝的数据传递给它 (line 166)。
    • RecentsView.applyLoadPlan(ArrayList<GroupTask> taskGroups) (line 1666) 被执行:
      • 它清空当前的视图。
      • 遍历接收到的 taskGroups 列表。
      • 对于每个 GroupTask,它从视图池 (mTaskViewPool, mGroupedTaskViewPool, etc. line 507-509) 中获取或创建一个 TaskView (或其子类)。
      • 调用 taskView.bind(task, ...) 等方法,将 Task 对象中的数据(如应用名称、图标、缩略图占位符等)绑定到 TaskView 上。
      • 将填充好数据的 TaskView 添加到 RecentsView 中 (addView(tv) line 1788)。
      • 更新滚动范围、ClearAll 按钮状态等。
  5. 系统更新通知 (System Update Notification):

    • RecentTasksList 在初始化时通过 mSysUiProxy.registerRecentTasksListener(...) (line 76) 注册了一个监听器。
    • 当系统(如 SystemUI)中的最近任务列表发生变化时,会通过 Binder 回调 IRecentTasksListener.onRecentTasksChanged()
    • 这个回调被调度到主线程执行 RecentTasksList.this::onRecentTasksChanged (line 80)。
    • onRecentTasksChanged (line 185) 调用 invalidateLoadedTasks() (line 189),这会递增 mChangeId 并将 mResultsUimResultsBg 标记为无效。
    • 这保证了下一次 RecentsView 调用 reloadIfNeeded() 时,会触发一次新的数据加载流程,而不是使用过期的缓存。

总结:

Launcher 的最近任务加载是一个精心设计的流程,它通过 RecentsModel 解耦了 UI 和数据获取,利用 RecentTasksList 处理与系统的交互和基本缓存。通过异步加载避免阻塞 UI 线程,并通过 mChangeId 和系统回调确保数据在需要时能得到及时更新,同时利用缓存 (mResultsUi, mResultsBg) 提高了效率。图标和缩略图的加载/缓存则由 RecentsModel 中的 TaskIconCacheTaskThumbnailCache 独立处理,并通过 TaskVisualsChangeListener 接口将更新通知给 RecentsView

相关文章:

  • Java面试实战:从Spring Boot到微服务的深入探讨
  • 双周报Vol.70: 运算符重载语义变化、String API 改动、IDE Markdown 格式支持优化...多项更新升级!
  • 用Java实现简易区块链:从零开始的探索
  • 智能电网第1期 | 工业交换机在变电站自动化系统中的作用
  • 【云馨AI-大模型】Dify 1.2.0:极速集成 SearXNG,畅享智能联网搜索新境界,一键脚本轻松部署SearXNG
  • 基于STM32、HAL库的MCP41010T数字电位器驱动程序设计
  • idea快捷键 Project tool window
  • 【Linux网络与网络编程】07.应用层协议HTTPS
  • 眼镜眨巴眨巴-一步几个脚印从头设计数字生命2——仙盟创梦IDE
  • 国产紫光同创FPGA实现SDI视频编解码+图像缩放,基于HSSTHP高速接口,提供2套工程源码和技术支持
  • 国产紫光同创FPGA实现SDI视频编解码,基于HSSTHP高速接口,提供3套工程源码和技术支持
  • 【Python】Selenium切换网页的标签页的写法(全!!!)
  • 学习思路分享---从0开始搭建基本web服务器
  • 飞搭系列 | 组件增加标记,提升用户体验
  • 动态加载内容时selenium如何操作?
  • KDD Cup 2017 数据集分析
  • 快速定位达梦缓存的执行计划并清理
  • HTML页面结构最佳实践方案
  • Phyton简介与入门
  • TextCNN 模型文本分类实战:深度学习在自然语言处理中的应用
  • 厦门国贸去年营收约3544亿元,净利润同比减少67.3%
  • 新“出差三人组”亮相!神二十乘组简历来了
  • “听公交时听一听”,上海宝山街头遍布“有声图书馆”
  • 美股反弹,纳斯达克中国金龙指数大涨3.69%
  • 依托空域优势,浦江镇将建设上海首个“低空融合飞行示范区”
  • 中纪委驻中组部纪检监察组原组长李刚被捕