Android學習總結之RecyclerView補充篇

在 Android 開發中,列表數據更新的性能一直是關鍵痛點。傳統的?notifyDataSetChanged()?會觸發全量刷新,導致不必要的界面重繪。而?DiffUtil?作為 Android 提供的高效差異計算工具,能精準識別數據變化,實現局部更新,成為 RecyclerView 性能優化的核心武器。本文將從原理、使用步驟、進階技巧到常見錯誤,全面解析這一重要工具。

一、DiffUtil 核心原理:高效差異計算的基石

為什么需要 DiffUtil?

  • 傳統更新的缺陷:直接調用?notifyDataSetChanged()?會重建所有 Item,即使只有少數數據變化,也會導致全局刷新,浪費 CPU 資源。
  • DiffUtil 的價值:通過兩次遍歷(預掃描和反向掃描)生成差異列表,僅對插入、刪除、移動、變更的 Item 執行最小化更新,大幅減少 UI 操作。

核心方法解析(DiffUtil.Callback

  1. getOldListSize()?&?getNewListSize()
    返回新舊數據集的大小,是差異計算的基礎。
  2. areItemsTheSame(oldPos, newPos)
    判斷新舊列表中指定位置的 Item 是否為同一個(通常通過唯一 ID 比較)。
    關鍵作用:確定是否可復用 ViewHolder,避免重復創建視圖。
  3. areContentsTheSame(oldPos, newPos)
    判斷 Item 內容是否發生變化(如字段修改)。
    返回?false?時:觸發?onBindViewHolder?全量更新。
  4. getChangePayload(oldPos, newPos)(可選)
    返回差異化數據(如僅標題變更),用于實現更細粒度的局部更新(跳過未變化的控件)。

二、使用步驟:從數據對比到局部更新

1. 定義 DiffUtil.Callback(核心步驟)

val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() {// 舊數據集大小override fun getOldListSize(): Int = oldList.size// 新數據集大小override fun getNewListSize(): Int = newList.size// 判斷是否為同一個 Item(建議用唯一 ID 比較)override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {return oldList[oldPos].id == newList[newPos].id}// 判斷內容是否變化(建議重寫 equals 或字段對比)override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {val oldItem = oldList[oldPos]val newItem = newList[newPos]return oldItem.title == newItem.title && oldItem.imageUrl == newItem.imageUrl}// 可選:返回差異化載荷(如僅標題變更)override fun getChangePayload(oldPos: Int, newPos: Int): Any? {val oldItem = oldList[oldPos]val newItem = newList[newPos]return if (oldItem.title != newItem.title) "UPDATE_TITLE" else null}
})

2. 在后臺線程計算差異(避免阻塞 UI)

// 在協程或異步線程中執行
GlobalScope.launch(Dispatchers.Default) {val diffResult = DiffUtil.calculateDiff(MyDiffCallback(oldList, newList))withContext(Dispatchers.Main) {// 更新數據集(先更新數據,再應用差異)oldList.clear()oldList.addAll(newList)diffResult.dispatchUpdatesTo(adapter) // 觸發局部刷新}
}

3. 在 Adapter 中啟用穩定 ID(提升效率)

class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {init {setHasStableIds(true) // 必須設置,否則 DiffUtil 無法正確復用 Item}override fun getItemId(position: Int): Long {return dataList[position].id // 返回唯一 ID}
}

4. 處理局部更新(可選,配合 payload)

在?onBindViewHolder?中根據?payloads?選擇性更新:

override fun onBindViewHolder(holder: MyViewHolder, position: Int, payloads: List<Any>
) {if (payloads.isEmpty()) {// 全量更新(首次加載或內容完全變化)holder.bind(dataList[position])} else {// 局部更新(僅處理變化的字段)payloads.forEach { payload ->when (payload) {"UPDATE_TITLE" -> holder.titleTextView.text = dataList[position].title// 其他 payload 處理...}}}
}

三、進階技巧:實現精準局部更新

1. 自定義載荷(Payload)的應用場景

  • 場景:當 Item 部分字段變化(如點贊數、未讀消息數),無需刷新整個視圖。
  • 優勢:跳過未變化控件的綁定邏輯,進一步減少 CPU 計算。

2. 處理數據移動與批量更新

  • 自動支持移動動畫:若數據順序變化(如排序),DiffUtil 會生成?notifyItemMoved?事件,配合?DefaultItemAnimator?實現平滑移動動畫。
  • 批量操作優化:使用?DiffUtil.DiffResult.dispatchUpdatesTo()?替代手動調用多個?notify?方法,確保動畫連貫。

3. 與 DataBinding 結合(Kotlin 擴展)

// 在 BindingAdapter 中處理 payload
@BindingAdapter("items")
fun setItems(recyclerView: RecyclerView, items: List<ItemData>) {val oldList = (recyclerView.adapter as MyAdapter).dataListDiffUtil.calculateDiff(object : DiffUtil.Callback() {// ... 同上 ...}).dispatchUpdatesTo(recyclerView.adapter as MyAdapter)
}

四、常見錯誤與避坑指南

1.?areItemsTheSame?實現錯誤

  • 錯誤示例:直接比較對象引用(oldItem == newItem),而非唯一 ID。
  • 后果:DiffUtil 誤判為不同 Item,導致重復創建 ViewHolder,性能下降。
  • 正確做法:使用業務唯一 ID(如數據庫主鍵、UUID)進行比較。

2. 忽略?setHasStableIds(true)

  • 后果:RecyclerView 無法通過 ID 快速匹配 Item,可能導致動畫異常或緩存失效。
  • 解決方案:在 Adapter 初始化時強制設置,并正確實現?getItemId()

3. 在 UI 線程計算差異

  • 風險:大數據集下阻塞主線程,導致界面卡頓(DiffUtil 時間復雜度為 O (N^2),N 為列表長度)。
  • 最佳實踐:始終在后臺線程執行?calculateDiff,通過?runOnUiThread?或協程切回主線程更新 UI。

4. 先調用?dispatchUpdatesTo?再更新數據集

  • 錯誤流程
diffResult.dispatchUpdatesTo(adapter) // 錯誤:此時舊數據未更新
oldList.clear()
oldList.addAll(newList)
  • 正確順序:先更新數據集,再應用差異(確保 Adapter 持有最新數據)。

5. 過度依賴?getChangePayload

  • 建議:僅在明確需要局部更新時實現該方法(如復雜布局中的單個控件變化),否則保持默認返回?null,避免邏輯復雜化。

五、最佳實踐總結

  1. 最小化差異計算范圍

    • 避免在?areItemsTheSame?和?areContentsTheSame?中執行復雜邏輯,確保快速返回結果。
    • 對大數據集(如萬級列表),考慮分頁加載或增量更新,減少單次計算量。
  2. 結合緩存機制

    • 配合 RecyclerView 的?mCachedViews?和?RecycledViewPool,讓 DiffUtil 復用的 ViewHolder 直接從緩存獲取,減少布局解析。
  3. 測試差異計算

    • 使用單元測試驗證?DiffUtil.Callback?的正確性,覆蓋增、刪、改、移等各種場景。
// 示例:測試 Item 移動是否正確識別
val oldList = listOf(Item(1, "A"), Item(2, "B"))
val newList = listOf(Item(2, "B"), Item(1, "A"))
val callback = MyDiffCallback(oldList, newList)
assertEquals(1, callback.getOldListSize()) // 錯誤示例,實際應為 2
  1. 性能監控

    • 通過 Android Profiler 監測?calculateDiff?的耗時,確保后臺線程執行無阻塞。
    • 對比使用前后的 CPU 占用和 FPS 變化,量化優化效果。

結語

? ? ? DiffUtil 是 RecyclerView 實現高效數據更新的關鍵工具,其核心在于通過精準的差異計算,將 UI 操作降到最低。掌握?areItemsTheSame?和?areContentsTheSame?的正確實現,合理利用 payload 進行局部更新,避免常見陷阱,能顯著提升列表界面的流暢度。

感謝觀看!!!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/74064.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/74064.shtml
英文地址,請注明出處:http://en.pswp.cn/web/74064.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Miniforge3高效管理 Python環境:2025年最新實踐指南

Miniforge3 高效管理 Python 環境:2025 年最新實踐指南 在現代開發中,靈活高效地管理 Python 環境至關重要。Miniforge3 作為一款輕量級 Conda 管理工具,不僅默認采用更新更快的 conda-forge 軟件源,還對 ARM 架構(例如 Apple M1/M2/M3)有著出色的適配性。相比于傳統的 …

山東大學軟件學院項目創新實訓開發日志(4)之中醫知識問答數據存儲、功能結構、用戶界面初步設計

目錄 數據庫設計&#xff1a; 功能設計&#xff1a; 用戶界面: 數據庫設計&#xff1a; --對話表 (1個對話包含多條消息) CREATE TABLE conversations ( conv_id VARCHAR(36) PRIMARY KEY, -- 對話ID user_id VARCHAR(36) NOT NULL, -- 所屬用戶 title VARCHAR(100), -- 對話…

交換機、路由器、VLAN、單臂路由、三層交換、STP

華為模擬安裝 1.依次安裝wincap 2.wireshark 3.virtual box 4.ensp 一、設置 1.virtual box設置 2.計算機防火墻允許以上程序 3.eNSP設置 路由器&#xff1a;AR2240 交換機&#xff1a;S5700、CE12800 防火墻USG6000V 交換機 一、交換機工作原理 1、回顧 二層交換機…

【藍橋杯】每日練習 Day15

目錄 前言 奶牛選美 分析 代碼 大臣的旅費 分析 代碼 飛機降落 分析 代碼 母親的牛奶 分析 代碼 掃雷 分析 代碼 前言 雖為誕辰&#xff0c;但也不忘完成每日的訓練。 今天給大家帶來五道dfs的題目&#xff0c;包括組合數&#xff0c;連通塊&#xff0c;數的…

ipconfig、ping、ipconfig/all 4個常用 **Windows終端(CMD)命令** 的詳細解釋

ipconfig、ping、ipconfig/all 4個常用 Windows終端&#xff08;CMD&#xff09;命令 的詳細解釋、用途分析和使用示例 1. ipconfig 作用 快速查看本地網絡連接的 IP地址、子網掩碼、默認網關 等基礎信息。 示例輸出 Windows IP 配置無線局域網適配器 WLAN:IPv4 地址 . . .…

@emotion/css + react+動態主題切換

1.下載插件 npm install --save emotion/css 2.創建ThemeContext.tsx // src/ThemeContext.tsx import React, { createContext, useContext, useState } from "react";// 定義主題類型 export type Theme "light" | "dark";// 定義主題上下…

【信奧一本通提高篇】基礎算法之貪心算法

原文 https://bbs.fmcraft.top/blog/index.php/archives/22/ 貪心算法 概述 近年來的信息學競賽試題&#xff0c;經常出現求一個問題的可行解或最優解的題目。這類問題就是我們通常所說的最優化問題。貪心算法是求解這類問題的一種常用算法。在眾多的算法中&#xff0c;貪心…

CentOS-7.0系統基礎操作

配置ip地址 編輯網卡文件&#xff1a; vi etc/sysconfig/network-scripts/ifcfg-ens33 在網卡文件里參照如下設置&#xff1a; BOOTPROTO"static" IPADDR192.168.61.233 GATEWAY192.168.61.2 NETMASK255.255.255.0 ONBOOT"yes" 防火墻管理 開啟防火墻&am…

【大模型應用】信息抽取的調研

老規矩&#xff0c;先占坑&#xff0c;后續更新。 關鍵詞&#xff1a; Pydantic functioncal 參考文獻&#xff1a;小白學大模型&#xff1a;自定義信息抽取Agent-CSDN博客

MySQL內存使用率高問題排查與解決方案:

目錄標題 **一、問題現象****二、核心排查步驟****1. 參數檢查****2. 內存使用分析****3. 存儲過程/函數/視圖檢查****4. 操作系統級檢查** **三、解決方案****1. 調整MySQL配置****2. 關閉透明大頁&#xff08;THP&#xff09;****3. 優化查詢與存儲過程****4. 硬件與環境優化…

華為GaussDB數據庫的手動備份與還原操作介紹

數據庫的備份以A機上的操作為例。 1、使用linux的root用戶登錄到GaussDB服務器。 2、用以下命令切換到 GaussDB 管理員用戶&#xff0c;其中&#xff0c;omm 為當前數據庫的linux賬號。 su - omm 3、執行gs_dump命令進行數據庫備份&#xff1a; 這里使用gs_dump命令進行備…

How to install OpenJ9 JDK 17 on Ubuntu 24.04

概述 OpenJ9 是一款由 IBM 開發并開源的 Java 虛擬機&#xff08;JVM&#xff09;&#xff0c;現由 ?Eclipse 基金會管理&#xff08;名為 ?Eclipse OpenJ9&#xff09;。它旨在提供高性能、低內存消耗和快速啟動時間&#xff0c;特別適用于云原生和容器化環境。 關鍵特性 …

洛谷題單1-P5705 【深基2.例7】數字反轉-python-流程圖重構

題目描述 輸入一個不小于 100 100 100 且小于 1000 1000 1000&#xff0c;同時包括小數點后一位的一個浮點數&#xff0c;例如 123.4 123.4 123.4 &#xff0c;要求把這個數字翻轉過來&#xff0c;變成 4.321 4.321 4.321 并輸出。 輸入格式 一行一個浮點數 輸出格式 …

【云服務器】在Linux CentOS 7上快速搭建我的世界 Minecraft 服務器搭建,并實現遠程聯機,詳細教程

【云服務器】在Linux CentOS 7上快速搭建我的世界 Minecraft 服務器搭建&#xff0c;詳細詳細教程 一、 服務器介紹二、下載 Minecraft 服務端三、安裝 JDK 21四、搭建服務器五、本地測試連接六、添加服務&#xff0c;并設置開機自啟動 前言&#xff1a; 推薦使用云服務器部署&…

內網穿透_ZeroTiers部署_廣和通SC171_aidlux_嵌入式

下載 sudo curl -s https://install.zerotier.com | sudo bash &#xff08;需要科學上網&#xff09; 所有涉及硬件的操作好像都需要 root 權限&#xff0c;curl 在這里需要連接網絡&#xff0c;所以也需要 sudo sudo zerotier-cli status 若返回 200 info 及設備 ID&#xff…

Faster RCNN Pytorch 實現 代碼級 詳解

基本結構&#xff1a; 采用VGG提取特征的Faster RCNN. self.backbone:提取出特征圖->features self.rpn:選出推薦框->proposals self.roi heads:根據proposals在features上進行摳圖->detections features self.backbone(images.tensors)proposals, proposal_losses…

【Matlab】-- 基于MATLAB的美賽常用多種算法

文章目錄 文章目錄 01 內容概要02 各種算法基本原理03 部分代碼04 代碼下載 01 內容概要 本資料集合了多種數學建模和優化算法的常用代碼資源&#xff0c;旨在為參與美國大學生數學建模競賽&#xff08;MCM/ICM&#xff0c;簡稱美賽&#xff09;的參賽者提供實用的編程工具和…

Vue2和Vue3響應式的基本實現

目錄 簡介Vue2 響應式Vue2 響應式的局限性 Vue3 響應式Vue3 響應式的優點 Vue2 和 Vue3 響應式對比 簡介 在 Vue 框架中&#xff0c;數據的響應式是其核心特性之一。當頁面數據發生變化時&#xff0c;我們希望界面能自動更新&#xff0c;而不是手動操作 DOM。這就需要對數據進…

Linux系統中快速安裝docker

1 查看是否安裝docker 要檢查Ubuntu是否安裝了Docker&#xff0c;可以使用以下幾種方法&#xff1a; 方法1&#xff1a;使用 docker --version 命令 docker --version如果Docker已安裝&#xff0c;輸出會顯示Docker的版本信息&#xff0c;例如&#xff1a; Docker version …

ElasticSearch 分詞器

文章目錄 一、安裝中文分詞插件Linux安裝7.14.1版本&#xff1a;測試1&#xff1a;ik_smart測試2&#xff1a;ik_max_word 二、es內置的分詞器&#xff1a;三、拼音插件安裝以及&#xff08;IKpinyin使用&#xff09;配置 IK pinyin 分詞配置 一、安裝中文分詞插件 IK Analys…