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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/80284.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flink介绍——实时计算核心论文之Flink论文

引入 通过前面的文章&#xff0c;我们梳理了大数据流计算的核心发展脉络&#xff1a; S4论文详解S4论文总结Storm论文详解Storm论文总结Kafka论文详解Kafka论文总结MillWheel论文详解MillWheel论文总结Dataflow论文详解Dataflow论文总结 而我们专栏的主角Flink正是站在前人的…

极狐GitLab CEO 柳钢受邀出席 2025 全球机器学习技术大会

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 2025 年 4 月 18 日至 19 日&#xff0c;2025 全球机器学习技术大会&#xff08;ML-Summit 2025&#xff09;在上海隆重举行。…

Linux Sed 深度解析:从日志清洗到 K8s 等12个高频场景

看图猜诗&#xff0c;你有任何想法都可以在评论区留言哦~ 摘要&#xff1a;Sed&#xff08;Stream Editor&#xff09;作为 Linux 三剑客之一&#xff0c;凭借其流式处理与正则表达式能力&#xff0c;成为运维场景中文本批处理的核心工具。本文聚焦生产环境高频需求&#xff…

C++ STL 容器简介(蓝桥杯适用精简版)

C的万能头文件是&#xff1a; #include <bits/stdc.h> 一、常用 STL 容器 1.vector&#xff08;动态数组&#xff09; #include<iostream> #include<string> #include <vector> #include <algorithm> // 包含排序所需的头文件 using namespa…

Java语言的进化:JDK的未来版本

作为一名Java开发者&#xff0c;我们正处在一个令人兴奋的时代&#xff01;Java语言正在以前所未有的速度进化&#xff0c;每个新版本都带来令人惊喜的特性。让我们一起探索JDK未来版本的发展方向&#xff0c;看看Java将如何继续领跑编程语言界&#xff01;&#x1f4aa; &…

不要使用Round函数保留小数位了

不要使用Round函数保留小数位了 如果你表格不需要保留公式&#xff0c;那么就不要使用Round函数保留小数位了。用Excel工作圈插件&#xff0c;可以轻松以数值形式保留小数位&#xff0c;且支持合并单元格、不连贯区域快速处理。 如下图&#xff0c;有文本&#xff0c;有跨行合并…

【C++】入门基础【下】

目录 一、缺省参数二、函数重载1. 函数类型不同2. 参数个数不同3、函数类型顺序不同 三、引用1、引用的概念和定义2、引用的功能2.1 功能1&#xff1a; 做函数形参&#xff0c;修改形参影响实参2.2 功能2&#xff1a; 做函数形参&#xff0c;减少拷贝&#xff0c;提高效率2.3 功…

git比较不同分支的不同提交文件差异

背景&#xff1a;只想比较某2个分支的某2次提交的差异&#xff0c;不需要带上父提交。 以commitA为基准&#xff0c;用commitB去比较差异 直接上代码&#xff1a; commitAxxxx1 commitBxxxx2 outputFile"output.txt"# 获取与第一个父提交的文件列表 filesA$(git di…

Linux内核之struct pt_regs结构

前沿 项目开发最近进行系统hook功能实现相关业务&#xff0c;主要在centos7和8系列环境开发下关功能。调研了相关知识点&#xff0c;发现在系统7和8上内核版本差别比较大&#xff0c;7-3.10.x系列版本&#xff0c;8-4.18.x系列版本。依据两个系统的内核情况根对应的内核符号表进…

《从混乱到有序:ArkUI项目文件结构改造指南》

在ArkUI开发的广袤天地里&#xff0c;构建一个清晰、有序的文件结构&#xff0c;是打造优质应用的关键。一个合理的文件结构&#xff0c;就像为开发者精心绘制的地图&#xff0c;在项目的各个阶段&#xff0c;都能提供明确的指引&#xff0c;让开发过程顺畅无阻。今天&#xff…

C#基于Sunnyui框架和MVC模式实现用户登录管理

C#基于Sunnyui框架和MVC模式实现用户登录管理 1 Controller1.1 UserManagementController.cs&#xff08;控制器入口&#xff09; 2 Model2.1 UserRepository.cs&#xff08;用户管理模型&#xff09;2.2 User.cs&#xff08;用户结构体&#xff09;2.3 SQLiteHelper.cs&#x…

自然语言处理(NLP)技术的实例

自然语言处理&#xff08;NLP&#xff09;技术在各个领域都有广泛的应用&#xff0c;以下是几个例子&#xff1a; 语音识别&#xff1a;通过NLP技术&#xff0c;计算机可以识别和理解语音指令&#xff0c;例如智能助手如Siri和Alexa就是通过语音识别技术实现与用户的交互。 机…

Spring Boot实战(三十六)编写单元测试

目录 一、什么是单元测试&#xff1f;二、Spring Boot 中的单元测试依赖三、举例 Spring Boot 中不同层次的单元测试3.1 Service层3.2 Controller 层3.3 Repository层 四、Spring Boot 中 Mock、Spy 对象的使用4.1 使用Mock对象的背景4.2 什么是Mock对象&#xff0c;有哪些好处…

aws服务(四)文件存储服务S3 介绍使用代码集成

一、介绍 1、简介 Amazon S3 是 Amazon Web Services 提供的一种对象存储服务(Object Storage),用于在云中存储和检索任意数量的数据。它以高可用性、高扩展性和高持久性著称,非常适合用来存储网站资源、数据备份、日志文件、大数据、机器学习输入输出等。 2、主要特性 …

应用信息1.13.0发布

增加工具箱 增加启动器功能 增加布局查看器 增加手动安装和卸载应用 增加APK文件解析 增加应用多选功能 增加查看应用预装版本 增加应用信息和ADB命令导出 修复其它问题... 百度下载&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;1234

【Vue3 实战】插槽封装与懒加载

一、为什么需要插槽&#xff1f;从一个面板组件说起 在电商首页开发中&#xff0c;经常遇到这样的场景&#xff1a; 「新鲜好物」「人气推荐」同样类型模块都需要相同的标题栏&#xff0c;但内容区布局不同 这时候&#xff0c;插槽&#xff08;Slot&#xff09;就像一个「内容…

虚无隧穿产生宇宙(true nothing tunneling) 是谁提出的

是 亚历克斯.维连金 英文名&#xff08;alex vilenkin 或者 Alexander Vilenkin)提出来的。 “虚无隧穿产生宇宙”&#xff08;true nothing tunneling&#xff09;这一概念并非一个标准的物理学术语&#xff0c;它更像是对某些现代宇宙学理论的描述&#xff0c;尤其是涉及宇宙…

postgis:添加索引时提示“对访问方法 gist 数据类型 geometry 没有默认的操作符表“

问题 在对gis表的geom字段创建空间索引时&#xff0c;出现“对访问方法 "gist" 数据类型 geometry 没有默认的操作符表”的提示报错。 解决方案 按系列步骤进行排查并解决。 1.先确认已安装postgis -- 查看postgis版本 SELECT postgis_full_version() 若安装了则…

图论---Prim堆优化(稀疏图)

题目通常会提示数据范围&#xff1a; 若 V ≤ 500&#xff0c;两种方法均可&#xff08;朴素Prim更稳&#xff09;。 若 V ≤ 1e5&#xff0c;必须用优先队列Prim vector 存图。 #include <iostream> #include <vector> #include <queue> #include <…

代码随想录算法训练营第一天:数组part1

今日学习的文章链接和视频链接 ● 自己看到题目的第一想法 ● 看完代码随想录之后的想法 ● 自己实现过程中遇到哪些困难 ● 今日收获&#xff0c;记录一下自己的学习时长 状态 思路理解完成 30% 代码debug完成 60% 代码模板总结并抽象出来 100% 题目 704 二分查找 题目链接…