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

news2025/5/14 2:47:34

最近任务是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 background
        mLoadingTasksInBackground = 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 callback
                    ArrayList<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.
     */
    @VisibleForTesting
    TaskLoadResult 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 it
        Collections.reverse(rawTasks);

        SparseBooleanArray tmpLockedUsers = new SparseBooleanArray() {
            @Override
            public boolean get(int key) {
                if (indexOfKey(key) < 0) {
                    // Fill the cached locked state as we fetch
                    put(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.java
    public 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.coloradmin.cn/o/2341035.html

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

相关文章

基于深度学习的校园食堂菜品智能结算系统

校园食堂菜品智能结算系统说明文档 1. 系统概述 本系统是一款基于YOLO深度学习算法的校园食堂菜品智能结算平台&#xff0c;旨在通过计算机视觉技术实现食堂菜品的自动识别与结算&#xff0c;提高结算效率&#xff0c;减少人工成本&#xff0c;优化用户体验。系统采用PyQt5框…

【UniApp】Vue2 scss 预编译器默认已由 node-sass 更换为 dart-sass

从 HBuilderX 4.56 &#xff0c;vue2 项目也将默认使用 dart-sass 预编译器。 vue2开发者sass预处理注意&#xff1a; sass的预处理器&#xff0c;早年使用node-sass&#xff0c;也就是vue2最初默认的编译器。 sass官方推出了dart-sass来替代。node-sass已经停维很久了。 另…

AI 硬件定制:开启智能新时代的钥匙

AI 硬件定制:开启智能新时代的钥匙 在科技飞速发展的当下,人工智能(AI)已不再是遥不可及的概念,它正以惊人的速度融入我们生活的方方面面。从智能手机中的语音助手,到工厂里的自动化生产线,AI 的身影无处不在。而在这股 AI 浪潮中,AI 硬件定制正逐渐崭露头角,成为推动…

SpringBoot中配置文件的加载顺序

下面的优先级由高到低 命令行参数java系统属性java系统环境变量外部config文件夹的application-{profile}.ym文件外部的application-{profile}.ym文件内部config文件夹的application-{profile}.ym文件内部的application-{profile}.ym文件外部config文件夹的application.ym文件外…

hooker frida版just_trust_me.js 2025升级 支持boringssl unpinning

曾几何时&#xff0c;我翻版了 Xposed 的 just_trust_me.apk&#xff0c; just_trust_me.js 脚本仿佛是一张通行证&#xff0c;让我们在 SSL Pinning 的高墙前轻松穿越。 但时代变了。BoringSSL、Cronet、静态 inline hook、动态 verify callback……一切都变得更加隐蔽和棘手…

React Article模块

实现基础文章发布 安装富文本编辑器 使用useEffect钩子函数获取到channelList,对channelList函数进行一个遍历 渲染到option 实现表单校验 1给Form组件绑定onFinish()函数 拼接表单数据 上传封面 onChange函数获得的参数

机器学习第二篇 多变量线性回归

数据集&#xff1a;世界幸福指数数据集中的变量有幸福指数排名、国家/地区、幸福指数得分、人均国内生产总值、健康预期寿命、自由权、社会支持、慷慨程度、清廉指数。我们选择GDP per Capita和Freedom&#xff0c;来预测幸福指数得分。 文件一&#xff1a;linear&#xff0c;…

C语言对n进制的处理

先看一道题目: 从键盘获取一个正整数,如果把它转为16进制的数字,那么它是一个几位数呢?如果把它转为28进制又是一个几位数呢? 在讲这个题目之前,我们先要了解进制转换 什么是进制转换&#xff1f; 简单来说&#xff0c;进制就是数位的表示方法。 十进制&#xff08;常用&am…

Ubuntu数据连接访问崩溃问题

目录 一、分析问题 1、崩溃问题本地调试gdb调试&#xff1a; 二、解决问题 1. 停止 MySQL 服务 2. 卸载 MySQL 相关包 3. 删除 MySQL 数据目录 4. 清理依赖和缓存 5.重新安装mysql数据库 6.创建程序需要的数据库 三、验证 1、动态库更新了 2、头文件更新了 3、重新…

Spark-Streaming简介和核心编程

Spark-Streaming简介 概述&#xff1a;用于流式数据处理&#xff0c;支持Kafka、Flume等多种数据输入源&#xff0c;可使用Spark原语运算&#xff0c;结果能保存到HDFS、数据库等。它以DStream&#xff08;离散化流&#xff09;为抽象表示&#xff0c;是RDD在实时场景的封装&am…

Docker 快速入门教程

1. Docker 基本概念 镜像(Image): 只读模板&#xff0c;包含创建容器的指令 容器(Container): 镜像的运行实例 Dockerfile: 用于构建镜像的文本文件 仓库(Repository): 存放镜像的地方&#xff08;如Docker Hub&#xff09; 2. 安装Docker 根据你的操作系统选择安装方式:…

【锂电池SOH估计】BP神经网络锂电池健康状态估计,锂电池SOH估计(Matlab完整源码和数据)

目录 效果一览程序获取程序内容研究内容基于BP神经网络的锂电池健康状态估计研究摘要关键词1. 引言1.1 研究背景1.2 研究意义1.3 研究目标2. 文献综述2.1 锂电池SOH估计理论基础2.2 传统SOH估计方法2.3 基于BP神经网络的SOH估计研究进展2.4 研究空白与创新点3. BP神经网络原理3…

Python常用的第三方模块之二【openpyxl库】读写Excel文件

openpyxl库模块是用于处理Microsoft Excel文件的第三方库&#xff0c;可以对Excel文件中的数据进行写入和读取。 weather.pyimport reimport requests#定义函数 def get_html():urlhttps://www.weather.com.cn/weather1d/101210101.shtml #爬虫打开浏览器上的网页resprequests.…

成熟软件项目解决方案:360°全景影像显控软件系统

​若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/147425300 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、Open…

前端开发核心知识详解:Vue2、JavaScript 与 CSS

一、Vue2 核心知识点 1. Vue2 的双向绑定原理 Vue2 实现双向绑定主要依赖数据劫持与发布 - 订阅者模式。 利用Object.defineProperty方法对数据对象的属性进行劫持&#xff0c;为每个属性定义getter和setter。getter用于收集依赖&#xff0c;当视图中使用到该属性时&#xf…

JDK安装超详细步骤

&#x1f525;【JDK安装超详细步骤】 文章目录 &#x1f525;【JDK安装超详细步骤】1. 卸载系统自带的旧版JDK2. 安装JDK113. 验证安装是否成功4. 常见问题4.1 执行java -version提示命令未找到&#xff1f; 1. 卸载系统自带的旧版JDK 查询已安装的OpenJDK包。 rpm -qa | gre…

39.剖析无处不在的数据结构

数据结构是计算机中组织和存储数据的特定方式&#xff0c;它的目的是方便且高效地对数据进行访问和修改。数据结构表述了数据之间的关系&#xff0c;以及操作数据的一系列方法。数据又是程序的基本单元&#xff0c;因此无论是哪种语言、哪种领域&#xff0c;都离不开数据结构&a…

在离线 Ubuntu 环境下部署双 Neo4j 实例(Prod Dev)

在许多开发和生产场景中&#xff0c;我们可能需要在同一台服务器上运行多个独立的 Neo4j 数据库实例&#xff0c;例如一个用于生产环境 (Prod)&#xff0c;一个用于开发测试环境 (Dev)。本文将详细介绍如何在 离线 的 Ubuntu 服务器上&#xff0c;使用 tar.gz 包部署两个 Neo4j…

第十五届蓝桥杯 2024 C/C++组 下一次相遇

目录 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; 思路&#xff1a; 自己的思路详解&#xff1a; 更好的思路详解&#xff1a; 代码&#xff1a; 自己的思路代码详解&#xff1a; 更好的思路代码详解&#xff1a; 题目&#xff1a; 题目描述&#xf…

【2】CICD持续集成-k8s集群中安装Jenkins

一、背景&#xff1a; Jenkins是一款开源 CI&CD 系统&#xff0c;用于自动化各种任务&#xff0c;包括构建、测试和部署。 Jenkins官方提供了镜像&#xff1a;https://hub.docker.com/r/jenkins/jenkins 使用Deployment来部署这个镜像&#xff0c;会暴露两个端口&#xff…