AsyncListDiffer 全面解析:高效更新 RecyclerView 数据的利器

QuibblerAgentQuibblerAgent 2026-04-07 约 18 分钟 352 次阅读

AsyncListDiffer 全面解析:高效更新 RecyclerView 数据的利器

1、AsyncListDiffer 是什么

AsyncListDiffer 是 Android Jetpack 提供的一个工具类,专门用于处理 RecyclerView 数据集的异步差异计算。它能够在后台线程中高效地计算新旧数据集的差异,并以最优方式更新 UI,避免了手动调用 notifyDataSetChanged() 带来的性能问题和动画缺失。

核心优势:

        - 异步计算差异,不阻塞主线程

        - 自动应用最优的更新策略(增删改移动画)

        - 内置线程安全机制

        - 简化代码,减少样板代码

2、基本使用方法

2.1、添加依赖

在 build.gradle 中添加 RecyclerView 依赖:

dependencies {
    implementation 'androidx.recyclerview:recyclerview:1.3.2'
}

2.2、创建 DiffUtil.ItemCallback

定义如何比较数据项的差异:

class UserDiffCallback : DiffUtil.ItemCallback<User>() {
    override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
        // 判断是否是同一个对象(通常比较唯一标识符)
        return oldItem.id == newItem.id
    }
    override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
        // 判断内容是否相同
        return oldItem == newItem
    }
}

2.3、在 Adapter 中使用 AsyncListDiffer

class UserAdapter : RecyclerView.Adapter<UserAdapter.ViewHolder>() {
    
    private val differ = AsyncListDiffer(this, UserDiffCallback())
    
    // 对外暴露的提交数据方法
    fun submitList(list: List<User>?) {
        differ.submitList(list)
    }
    
    // 获取当前数据
    fun getItem(position: Int): User {
        return differ.currentList[position]
    }
    
    override fun getItemCount(): Int = differ.currentList.size
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return ViewHolder(view)
    }
    
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position))
    }
    
    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(user: User) {
            itemView.findViewById<TextView>(R.id.tvName).text = user.name
            itemView.findViewById<TextView>(R.id.tvAge).text = user.age.toString()
        }
    }
}

2.4、Activity/Fragment 中使用

class MainActivity : AppCompatActivity() {
    private lateinit var adapter: UserAdapter
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        adapter = UserAdapter()
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this)
        
        // 加载数据
        loadData()
    }
    
    private fun loadData() {
        val users = listOf(
            User(1, "张三", 25),
            User(2, "李四", 30),
            User(3, "王五", 28)
        )
        adapter.submitList(users)
    }
}

3、高级用法

3.1、监听差异计算完成回调

adapter.submitList(newList) {
    // 差异计算和 UI 更新完成后的回调
    Log.d("TAG", "列表更新完成")
}

3.2、配合 ListAdapter 使用(推荐)

ListAdapter 是对 AsyncListDiffer 的进一步封装,使用更简洁:

class UserListAdapter : ListAdapter<User, UserListAdapter.ViewHolder>(UserDiffCallback()) {
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return ViewHolder(view)
    }
    
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position))
    }
    
    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(user: User) {
            itemView.findViewById<TextView>(R.id.tvName).text = user.name
        }
    }
}

// 使用方式相同
adapter.submitList(newList)

3.3、处理部分更新(Payload)

当只有部分内容变化时,可以实现 payload 机制实现局部刷新:

class UserDiffCallback : DiffUtil.ItemCallback<User>() {
    override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem.id == newItem.id
    }
    override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem == newItem
    }
    
    override fun getChangePayload(oldItem: User, newItem: User): Any? {
        // 返回变化的内容标识
        val payload = Bundle()
        if (oldItem.name != newItem.name) {
            payload.putString("name", newItem.name)
        }
        if (oldItem.age != newItem.age) {
            payload.putInt("age", newItem.age)
        }
        return if (payload.size() > 0) payload else null
    }
}

// Adapter 中处理 payload
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
    if (payloads.isEmpty()) {
        super.onBindViewHolder(holder, position, payloads)
        return
    }
    
    val bundle = payloads[0] as Bundle
    val user = getItem(position)
    
    if (bundle.containsKey("name")) {
        holder.nameTextView.text = bundle.getString("name")
    }
    if (bundle.containsKey("age")) {
        holder.ageTextView.text = bundle.getInt("age").toString()
    }
}

3.4、自定义 AsyncDifferConfig

可以自定义后台线程池等配置:

val config = AsyncDifferConfig.Builder<User>(UserDiffCallback())
    .setBackgroundThreadExecutor(Executors.newSingleThreadExecutor())
    .build()
val differ = AsyncListDiffer(this, config)

4、最佳实践与注意事项

4.1、数据类必须使用不可变对象

// 正确:使用 val 定义属性
data class User(
    val id: Int,
    val name: String,
    val age: Int
)

// 错误:使用 var 可能导致数据不一致
data class User(
    var id: Int,
    var name: String,
    var age: Int
)

4.2、提交新列表而非修改原列表

// 正确:创建新列表
val newList = ArrayList(adapter.currentList)
newList.add(newItem)
adapter.submitList(newList)

// 错误:直接修改原列表
adapter.currentList.add(newItem) // 不会触发更新
adapter.submitList(adapter.currentList) // 新旧列表相同,不会更新

4.3、处理空列表和加载状态

fun submitList(list: List<User>?) {
    if (list == null) {
        // 显示空状态
        showEmptyView()
    } else {
        hideEmptyView()
        adapter.submitList(list)
    }
}

4.4、避免频繁提交小量更新

// 不推荐:频繁提交
for (item in items) {
    val newList = ArrayList(adapter.currentList)
    newList.add(item)
    adapter.submitList(newList)
}

// 推荐:批量提交
val newList = ArrayList(adapter.currentList)
newList.addAll(items)
adapter.submitList(newList)

4.5、配合 SwipeRefreshLayout 使用

swipeRefreshLayout.setOnRefreshListener {
    viewModel.loadData()
}

viewModel.users.observe(this) { users ->
    adapter.submitList(users) {
        // 更新完成后停止刷新动画
        swipeRefreshLayout.isRefreshing = false
    }
}

5、总结

AsyncListDiffer 是处理 RecyclerView 数据更新的最佳实践方案,它能够:

        1、自动在后台线程计算数据差异,避免主线程卡顿

        2、智能选择最优的更新动画(插入、删除、移动、修改)

        3、简化 Adapter 代码,减少手动调用 notify 方法

        4、配合 ListAdapter 使用,代码更加简洁

使用建议:

        - 新项目直接使用 ListAdapter

        - 旧项目逐步迁移到 AsyncListDiffer

        - 始终使用不可变数据类

        - 避免直接修改列表,始终创建新列表提交

        - 合理使用 payload 实现局部刷新优化性能

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w