RecyclerView with Images 阻塞 UI

RecyclerView with Images blocking the UI

我有一个 recyclerview,它加载一组主要显示图像的项目。我在后台检索这些项目,每批 100 个。我使用 Picasso 加载图像。图片很大,但我使用 fit() 调整了它们的大小。

每当使用 SwipeRefreshLayout 加载或刷新屏幕时,UI 阻塞不到一秒钟,但足以引起注意。如果我不加载图像而只加载文本,那么 UI 块就不会发生。

我在 Picasso 上放置了日志行,每次刷新都会检索 100 张图像,但我猜 Picasso 是在后台线程中工作吗?

适配器:

@ActivityScope
class LimitableListAdapter @Inject constructor() : RecyclerView.Adapter<LimitableListAdapter.ViewHolder>() {

    private var events: MutableList<Event> = mutableListOf()
    private var itemClick: ((Event, View) -> Unit)? = null


    override fun onBindViewHolder(holder: ViewHolder, position: Int) {

        val binding : ItemVideoGridScoreBinding = holder.binding

        var viewModel = binding.viewModel
        val event = events[position]

        //Unbind old viewModel if we have one
        viewModel?.unbind()

        // Create new ViewModel, set it, and bind it
        viewModel = EventViewModel(event)
        binding.viewModel = viewModel
        viewModel.bind()


        holder.setClickListener(itemClick)
    }

    override fun getItemCount(): Int = events.size


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = DataBindingUtil.inflate<ItemVideoGridScoreBinding>(
                LayoutInflater.from(parent.context),
                R.layout.item_video_grid_score,
                parent,
                false
        )
        return ViewHolder(binding)
    }


    fun updateEvents(events: List<Event>, stride: Int) {
        var size = this.events.size
        Timber.w("Updating with: " + events.joinToString(",", transform = { e -> e.id.toString() }))
        this.events = events.toMutableList()
        notifyDataSetChanged()
        /*if (size == 0) {
            Timber.w("branch 1")
            var mutableList = events.toMutableList()
            if(mutableList.size == 0)
                return
            mutableList.add(Event.mockEvent(stride))
            this.events.addAll(mutableList)
            notifyDataSetChanged()
        } else {
            if (size > 2) {
                Timber.w("branch 2.1")
                this.events.addAll(size - 1, events.toMutableList())
                notifyItemRangeChanged(size-1, events.size)
            }
            else {
                Timber.w("branch 2.2")
                this.events.addAll(size, events.toMutableList())
                notifyItemRangeChanged(size, events.size)
            }

        }*/
        Timber.i("New list is: " +  this.events.joinToString(",", transform = { e -> e.id.toString() }))
    }

    fun clearList(){
        this.events.clear()
        notifyDataSetChanged()
    }

    fun setClickListener(itemClick: ((Event, View) -> Unit)?) {
        this.itemClick = itemClick
    }


    class ViewHolder(val binding: ItemVideoGridScoreBinding) : RecyclerView.ViewHolder(binding.root) {
        fun setClickListener(callback: ((Event, View) -> Unit)?) {
            binding.viewModel.clicks().subscribe() {
                callback?.invoke(binding.viewModel.event, itemView)
            }
        }
    }

}

绑定实用程序:

@BindingAdapter({"app:coverUrl"})
    public static void loadCover(ImageView view, String imageUrl) {

            Picasso p = Picasso.with(view
                    .getContext());
                    p.setIndicatorsEnabled(true);

                    p.load(imageUrl)
                    .fit()
                    .centerInside()
                    .error(R.drawable.ic_broken_image)
                    .into(view);
        }
    }

xml:

(...)
     <ImageView
                    android:id="@+id/event_cover"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:scaleType="centerCrop"
                    app:coverUrl="@{videoItem.cover}"
                    tools:src="@drawable/img"
                    />
(...)

调整几张大图片的大小需要一些时间。虽然 Picasso 对调整大小的图像进行缓存,但第一次它仍然需要调整每个图像的大小(随后它只会使用缓存中调整大小的图像,因此花费的时间更少)。 此外,使用 fit 可能比仅使用 resize 花费更多时间,因为它需要根据布局计算大小。如果您可以自己计算静态大小(只需一次),然后使用 resize 应该可以缩短加载时间。

但大多数情况下,如果您的服务器可以在预览时向您发送较小的 images/thumbnails,然后如果您想在某些详细信息屏幕上查看全尺寸图像,那会更好。

由于您要加载 100 张图片,而且您提到图片很大, 所以它肯定会阻止 UI 线程,如果你想避免这种情况,那么你可以 resize 毕加索的图像:

Picasso.with(context)
        .load(-yourImgURL-)
        .resize(450, 200) // resizes image to desired dimensions here
        .into(-yourImageView);

假设您无法在服务器端创建缩略图(这将是最简单的解决方案),我的建议是采用以下方法之一

  1. 使用本地可绘制对象作为占位符。这不会阻塞 UI 线程,图像可以在后台加载。行为将类似于加载图像网格时 Instagram 的行为。如下所示。

p.load(imageurl).placeholder(R.drawable.localFile).fit().centerInside().error(R.drawable.ic_broken_image).into(view)

  1. 使用滑翔。 Glide 确实具有创建缩略图的强大功能。并且您可以比完整图像更快地将缩略图加载到视图中。