使用Glide在RecyclerView中加载图片时,UI出现卡顿和不流畅的情况

7
我有一个RecyclerView,使用Glide从URL加载图像。现在,这些URL是使用分页从Firebase检索的,如下所示。问题在于当MainActivity(包含下面的代码和RecyclerView)首次初始化时,界面会出现明显的滞后(非常卡顿和抖动的滚动,选项菜单需要3秒钟才能打开等),并且图像需要一段时间才能加载。但是,当我向下滚动并到达第一页数据的末尾时,OnScrollListener将被触发,并开始从新查询中加载新数据。我已经尝试了根据我在另一篇文章中得到的建议优化Glide所做的事情,还将adapter.setHasFixedSize设置为true,但没有成功。有什么想法吗?我是否通过某种方式挂起了UI线程,尽管查询是异步的?
编辑:由于Glide不得不将多个图像加载到recyclerView的imageViews中,它会导致主线程出现延迟。如果是这样,我该怎么办来解决这个问题?
以下是我如何处理从Firebase获取的数据的分页并通知适配器的方式:
class MainActivity : AppCompatActivity() {

private val TAG: String = MainActivity::class.java.simpleName // Tag used for debugging
private var queryLimit : Long = 50 // how many documents should the query request from firebase
private lateinit var iconsRCV : RecyclerView // card icons recycler view
private lateinit var lastVisible:DocumentSnapshot

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val rootRef: FirebaseFirestore = FirebaseFirestore.getInstance()
    val urlsRef : CollectionReference = rootRef.collection("CardIconUrls")
    val query : Query = urlsRef.orderBy("resID",Query.Direction.ASCENDING).limit(queryLimit) // create a query for the first queryLimit documents in the urlsRef collection

    // Setting Toolbar default settings
    val toolbar : Toolbar = findViewById(R.id.mainToolbar)
    setSupportActionBar(toolbar) // set the custom toolbar as the support action bar
    supportActionBar?.setDisplayShowTitleEnabled(false) // remove the default action bar title

    // RecyclerView initializations
    iconsRCV = findViewById(R.id.cardIconsRCV)
    iconsRCV.layoutManager = GridLayoutManager(this,5) // set the layout manager for the rcv
    val iconUrls : ArrayList<String> = ArrayList() // initialize the data with an empty array list
    val adapter = CardIconAdapter(this,iconUrls) // initialize the adapter for the recyclerview
    iconsRCV.adapter = adapter // set the adapter
    iconsRCV.setHasFixedSize(true)

    iconsRCV.addOnScrollListener(object:OnScrollListener(){
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)

            if(!iconsRCV.canScrollVertically(1) && (newState == RecyclerView.SCROLL_STATE_IDLE) && ((iconsRCV.layoutManager as GridLayoutManager).findLastVisibleItemPosition() == (iconsRCV.layoutManager as GridLayoutManager).itemCount-1)) {
                Log.d(TAG,"End of rcv-Starting query")
                val nextQuery = urlsRef.orderBy("resID",Query.Direction.ASCENDING).startAfter(lastVisible).limit(queryLimit).get().addOnCompleteListener { task ->
                    if(task.isSuccessful) {
                        Log.d(TAG,"Next query called")
                        for(document:DocumentSnapshot in task.result!!) {
                            iconUrls.add(document.get("url").toString())
                        }
                        lastVisible = task.result!!.documents[task.result!!.size()-1]
                        adapter.notifyDataSetChanged()
                    }
                }
            }
        }
    })

    query.get().addOnCompleteListener {task: Task<QuerySnapshot> ->
        if(task.isSuccessful) {
            Log.d(TAG,"Success")
            for(document:DocumentSnapshot in task.result!!) {
                Log.d(TAG,"Task size = " + task.result!!.size())
                iconUrls.add(document.get("url").toString()) // add the url to the list
            }
            lastVisible = task.result!!.documents[task.result!!.size()-1]
            adapter.notifyDataSetChanged() // notify the adapter about the new data
        }
    }
}

这是RecyclerView的适配器:

public class CardIconAdapter extends RecyclerView.Adapter<CardIconAdapter.ViewHolder> {

    private List<String> urlsList;
    private Context context;

    class ViewHolder extends RecyclerView.ViewHolder {
        ImageView iconImg;
        ViewHolder(@NonNull View view) {
            super(view);
            iconImg = view.findViewById(R.id.cardIcon);
        }
    }

    public CardIconAdapter(Context cntxt, List<String> data) {
        context = cntxt;
        urlsList = data;
    }

    @NonNull
    @Override
    public CardIconAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view =  LayoutInflater.from(parent.getContext()).inflate(R.layout.card_icons_rcv_item,parent,false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull CardIconAdapter.ViewHolder holder, int position) {
        RequestOptions requestOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL);
        GlideApp.with(context).load(urlsList.get(position)).thumbnail(0.25f).centerCrop().dontTransform().apply(requestOptions).into(holder.iconImg);
    }

    @Override
    public int getItemCount() {
        return urlsList.size();
    }
}

scrollingup


你的Recycler View实现有一点不同。请提供Firebase数据库的节点结构,说明数据如何存储。 - Radhey
@Radhey,我的节点结构非常简单:CardIconUrls -> (文档)-> 字段:url。该集合是CardIconUrls,其中包含大约1.5k个文档,每个文档都有2个字段:1)id和2)url字符串。 - Stelios Papamichail
@SteliosPapamichail 这些图片是gif格式的吗? - A Farmanbar
@Mr.AF 尽管在 Firebase 中它们的格式显示为 .png 或 .jpg 文件,但当我打开其中的前 46 个时,它们似乎是 gif(它们是动画的)。但只有前 46 个。 - Stelios Papamichail
好的。我需要项目的链接。如果您能创建一个包含问题的示例,我会检查它。我认为这只是一个简单的错误。 - Alexander
显示剩余6条评论
5个回答

8
经过反复尝试,我终于找到了问题所在。我的第一个错误是没有发布recyclerview项的xml布局文件,因为这是性能问题的来源。第二个错误是我使用了LinearLayout,并将其自己的layout_width和layout_height属性设置为75dp,而不是ImageView的属性,后者嵌套在LinearLayout中,并对ImageView的相应属性使用wrap_content。因此,为了解决性能问题,我做了以下几点:
  1. 我将LinearLayout更改为ConstraintLayout(据我所知,它在一般情况下更加优化)。
  2. 我将ConstraintLayout的layout_widthlayout_height属性设置为wrap_content,最后
  3. 我将实际的ImageView的layout_width和layout_height属性设置为75dp,这是我想要的实际大小。
这是在更改后每个recyclerview项的最终布局文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="horizontal"
    android:padding="5dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/cardIcon"
        android:layout_width="75dp"
        android:layout_height="75dp"
        android:contentDescription="cardIcon"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:srcCompat="@tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>

2
一些修复加载大量图片时出现卡顿的方法。
  • Setting fixed size true to your RecyclerView but it wont help you if pagination involves

    mRecyclerView.setHasFixedSize(true);
    
  • Overriding pixels of the image using glide like this

    Glide.with(this)
         .load(YOUR_URL)
         .apply(new RequestOptions().override(dimenInPx, dimenInPx)
         .placeholder(R.drawable.placeHolder).error(R.drawable.error_image))
         .into(imageview);
    
  • Add hardwareAccelerated="true" property to your activity tag inside manifest like

    <activity
         ...
         android:hardwareAccelerated="true"
         ...
    />
    

我认为这可能会有所帮助。


很遗憾,我没有看到任何区别,我的意思是它们在加载了大约2分钟后仍然没有进展,一直在不停地加载。这就是UI的样子:https://imgur.com/a/Ie45UCi。我还注意到,在logcat中我没有得到任何实际的错误信息,所以我觉得很难找到这个问题的根源。 - Stelios Papamichail
如果您需要显示加载进度,请覆盖图像的高度和宽度以提高RecyclerView的性能。请参考此链接:https://dev59.com/sVsW5IYBdhLWcg3wKkne#55632581 - Arunachalam k

0

这个Github上的答案非常有帮助

基本上,如果你设置了图像的确切尺寸,它就不会一直计算图像需要多大,从而显著提高了性能。通过在我加载的ImageView上设置定义的高度和宽度,这有助于将我的RecyclerView从无法使用变为完全正常。


0
相信我。不要像接受的答案中那样使用ConstraintLayout。与LinearLayout相比,它的性能较低。根据我的测量,使用ConstraintLayout膨胀一个布局文件平均需要16毫秒,而使用LinearLayout的布局文件只需10毫秒。为了实现平滑的滚动效果,至少要在16毫秒内膨胀布局。
在Glide示例应用程序这里中,他们创建了一个返回相同宽度和高度的SquareImageView
class SquareImageView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) :
    AppCompatImageView(context, attrs, defStyleAttr) {

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, widthMeasureSpec)
    }
}

别忘了在 attrs.xml 中声明 styleable

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <com.example.SquareImageView
        android:id="@+id/image_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="@dimen/padding_normal"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="@dimen/padding_small"
        android:textAlignment="center"
        android:textColor="@color/secondaryTextColor"
        android:textSize="15sp" />
</LinearLayout>

RecyclerView中尽量避免使用ConstraintLayout,因为它会导致在首次填充ViewHolders时出现一些延迟。

另一个选择是预先填充一些视图并在onCreateViewHolder中使用它们。

init {
    val layoutInflater = LayoutInflater.from(context)
    mPreInflated = List(20) {
        MyLayoutBinding.inflate(layoutInflater, null, false)
    }.iterator()
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    val binding = if (mPreInflated.hasNext()) {
        mPreInflated.next()
    } else {
        val layoutInflater = LayoutInflater.from(context)
        MyLayoutBinding.inflate(layoutInflater, null, false)
    }
    return MyViewHolder(binding)
}

-1

我实际上正在分页数据,正如您在我的代码中所看到的。我加载数据页面,每个页面包含50个图标,只有在滚动到回收视图底部后才会请求新的图标。我只是没有使用Paging库,因为我认为对于这样简单的数据(字符串),阅读所有文档等不值得。您是否尝试过类似的东西,并看到了显着的性能提升? - Stelios Papamichail

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接