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.pswp.cn/diannao/80284.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/80284.shtml
英文地址,請注明出處:http://en.pswp.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 二分查找 題目鏈接…