Android RecyclerView 在 notifyDataSetChanged 调用上冻结 UI

Posted

技术标签:

【中文标题】Android RecyclerView 在 notifyDataSetChanged 调用上冻结 UI【英文标题】:Android RecyclerView freezes UI on a notifyDataSetChanged call 【发布时间】:2021-06-06 11:24:24 【问题描述】:

我知道关于这个话题有很多问题,但我仍然找不到解决方案。

我有一个应用程序,可以加载和显示图像(就像智能手机上的常规图库应用程序一样)

我使用协程在后台加载所有图像 - 在这个过程的这个阶段没有问题。

当我通知我的适配器有关数据时会出现问题。 notifyDataSetChanged() 调用会冻结 UI 片刻。

我们谈论的是相对较大的网格(例如 1000 多张照片)

几乎所有其他线程中的答案都表明,我应该检查哪些项目发生了变化 并调用 notifyItemChanged(index) 而不是调用 notifyDataSetChanged() 或使用 DiffUtil 或类似的东西。我明白这一切,但在 RecyclerView 初始化(当应用程序加载时)没有现有数据,所以我没有看到其他选项调用 notifyDataSetChanged()。

适配器代码:

class PhotosAdapter(private val mFragment: PhotosFragment, private val mListener: ClickListener) : RecyclerView.Adapter<PhotoViewHolder>() 

    private var photos: List<PhotoFile>? = null
    
    init 
        mFragment.mPhotosViewModel.photos.observe(mFragment, Observer 
            it?.let 
                photos = it
                notifyDataSetChanged()
            
        )
    

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoViewHolder 
        return PhotoViewHolder.from(parent)
    

    override fun onBindViewHolder(holder: PhotoViewHolder, position: Int) = holder.bind(
        position, photos!!.get(position), mFragment, mListener
    )

    override fun getItemCount(): Int = photos?.size ?: 0


class PhotoViewHolder private constructor(private val mBinding: PhotoGridItemBinding) : RecyclerView.ViewHolder(mBinding.root) 

    companion object 
        fun from(parent: ViewGroup) : PhotoViewHolder 
            val layoutInflater = LayoutInflater.from(parent.context)
            val binding = PhotoGridItemBinding.inflate(layoutInflater, parent, false)
            return PhotoViewHolder(binding)
        
    


    fun bind(index: Int, photo: PhotoFile, photosFragment: PhotosFragment, listener: ClickListener) 
        mBinding.index = index
        mBinding.photo = photo
        mBinding.viewHolder = this
        mBinding.listener = listener
        
        Glide.with(photosFragment).load(photo.uri)
            .thumbnail(0.1f).into(mBinding.photoGridItemThumbnail)

        mBinding.executePendingBindings()
    

问候,L

【问题讨论】:

您使用什么来加载回收站视图项目上的图像? glide、picasso、其他库还是原生方式? 你说:at the RecyclerView initialization (when the application loads) there is no existing data, so I don't see other options as calling notifyDataSetChanged().我不明白这个。如果没有数据,为什么需要调用 notifyDataSetChanged?请粘贴您的 RecyclerView 适配器的相关部分。 正如@MartinMarconcini 所说,一开始您应该设置和附加您的适配器、布局,仅此而已。 @MartinMarconcini 怎么样?好久不见。 @KristyWelsh 哈哈!!! :)))))(对不起 Stack Overflow Mods,我们一起工作过) 【参考方案1】:

我相信您的架构缺少最后一点。

你说:

“在 RecyclerView 初始化时(应用程序加载时)没有现有数据,所以我没有看到其他选项调用 notifyDataSetChanged()”

我不是 100% 确定我理解你的意思,但我相信你正在经历的是:

    应用启动,您设置了 Adapter/RecyclerView 但仍然没有数据;您还会从您的 ViewModel 中观察到一些 LiveData&lt;List&lt;T&gt;&gt; 或类似内容。 您向 ViewModel 请求数据,ViewModel 以一种或另一种方式暂停,并使用适配器的数据异步传送List&lt;T&gt;。 从适配器收到List&lt;T&gt; 后,您可以通过添加的某种方法设置它,例如adapter.setData(list),然后调用adapter.notifyDataSetChanged()

这需要很长时间。

其他线程推荐你使用DiffUtil()(或其AsyncDiffUtil版本)的原因是因为这有助于适配器......以及...... 适应 em> 您的数据到视图(持有人),通过计算需要 适配器 和什么是相同的。

(到目前为止)我们还没有看到您的适配器,但我猜您正在扩展 RecyclerView.Adapter&lt;T&gt;。这意味着你应该自己做 DiffUtil(或 Async)。

或者,您可以改为扩展 ListAdapter&lt;T, K&gt;,其中 T 是您的数据类型 (List&lt;XXX&gt;),K 是您的 ViewHolder 类型,如果我没记错的话,您可以使用 RecyclerView.ViewHolder(然后有你的 ViewHolders 扩展它)。

此列表适配器强制您在构造函数中提供 DiffUtil。

仅仅检查两个(空)列表之间的差异是没有意义的

并非如此,您正在制造一个框架已经为您提供解决方案的问题。 如果列表为空,DiffUtil 不会变慢(减去几毫秒?),因为它没有可比较的内容。但是当有数据时,它会为您完成繁重的工作。

在任何情况下,在您发布一些代码之前,我们都是空谈。 :)

【讨论】:

我已将适配器代码添加到原始问题中。问题是,我只需要填充 RecyclerView 一次,不会有进一步的更新。所以我实际上需要在应用加载时填充 RecyclerView。

以上是关于Android RecyclerView 在 notifyDataSetChanged 调用上冻结 UI的主要内容,如果未能解决你的问题,请参考以下文章

Android RecyclerView点击事件处理

Android:在recyclerview下方显示按钮,但始终在屏幕上

android之recyclerview的基本使用

android: ScrollView 内的 RecyclerView

Android:在 Recyclerview 中更改图像旋转

RecyclerView:错误的滚动效果(Android)