UI 使用 Glide 在 RecyclerView 中加载图像时滞后且不稳定
UI lags and is choppy when using Glide to load images in a RecyclerView
我有一个 RecyclerView,它使用 Glide 从 URL 加载图像。现在可以使用分页从 Firebase 检索 URL,如下所示。问题是,当 MainActivity(包含以下代码和 recyclerview)首次初始化时,UI 有很大的滞后(非常缓慢和不稳定的滚动,选项菜单需要 3 秒才能打开等)和图像需要一段时间才能加载。在我向下滚动并到达第一页数据的 RecyclerView 末尾后,触发 OnScrollListener 并且我开始从新查询加载新数据。我已经尽力根据用户对我制作的另一个 post 的建议来优化 Glide 的功能,并且我也将 adapter.setHasFixedSize
设置为 true ,但不幸的是。知道这里发生了什么吗?尽管查询是异步的,我是否以某种方式挂起 UI 线程?
编辑
:Glide 是否会因为必须将多个图像加载到回收器视图的 imageView 中而导致主线程延迟?如果是这样,我该如何应对?
以下是我如何处理从 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();
}
}
解决加载大量图片卡顿的一些方法
为您的 RecyclerView
设置固定大小,但如果涉及分页,它不会帮助您
mRecyclerView.setHasFixedSize(true);
像这样使用滑动覆盖图像的像素
Glide.with(this)
.load(YOUR_URL)
.apply(new RequestOptions().override(dimenInPx, dimenInPx)
.placeholder(R.drawable.placeHolder).error(R.drawable.error_image))
.into(imageview);
将 hardwareAccelerated="true"
属性 添加到清单中的 activity 标签,如
<activity
...
android:hardwareAccelerated="true"
...
/>
我认为这可能有所帮助。
尝试使用分页库结合recyclerview
link参考:
Paging Library for Android With Kotlin: Creating Infinite Lists
Android Jetpack: manage infinite lists with RecyclerView and Paging (Google I/O '18)
经过反复试验,我终于弄明白了这个问题。我的第一个错误是没有为 recyclerview 项目发布我的 xml 布局文件,因为这是性能问题的根源。第二个错误是我使用的是 LinearLayout 并将其自己的 layout_width 和 layout_height 属性设置为 75dp,而不是嵌套在 LinearLayout 内部并使用 wrap_content 作为ImageView各自的属性。因此,为了解决性能问题,我做了以下操作:
- 我将
LinearLayout
更改为 ConstraintLayout
(我读到它总体上更优化)
- 我将 ConstraintLayout 的
layout_width
& layout_height
属性设置为 wrap_content
最后
- 我将实际 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>
This answer from Github was very helpful
基本上,如果您设置图像的确切尺寸,Glide 就不必经常计算图像需要多大,从而显着提高性能。这有助于将我的 RecyclerView 从无法使用变为完美,只需在我正在加载的 ImageView 上设置定义的高度和宽度。
相信我。不要像接受的答案中那样使用 ConstraintLayout
。与 LinearLayout
相比,它的性能较低。根据我的测量,用 ConstraintLayout
扩展布局文件平均需要 16 毫秒,用 LinearLayout
扩展布局文件需要 10 毫秒。要有平滑的滚动效果,它必须至少在 16 毫秒内膨胀布局。
在 Glide 示例应用程序 here 中,他们创建了一个 SquareImageView
returns 相同的宽度和高度。
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
中声明样式化
<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
,因为它可能会在第一次膨胀 ViewHolder 时造成一些延迟。
另一种选择是预膨胀一些视图并在 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)
}
我有一个 RecyclerView,它使用 Glide 从 URL 加载图像。现在可以使用分页从 Firebase 检索 URL,如下所示。问题是,当 MainActivity(包含以下代码和 recyclerview)首次初始化时,UI 有很大的滞后(非常缓慢和不稳定的滚动,选项菜单需要 3 秒才能打开等)和图像需要一段时间才能加载。在我向下滚动并到达第一页数据的 RecyclerView 末尾后,触发 OnScrollListener 并且我开始从新查询加载新数据。我已经尽力根据用户对我制作的另一个 post 的建议来优化 Glide 的功能,并且我也将 adapter.setHasFixedSize
设置为 true ,但不幸的是。知道这里发生了什么吗?尽管查询是异步的,我是否以某种方式挂起 UI 线程?
编辑 :Glide 是否会因为必须将多个图像加载到回收器视图的 imageView 中而导致主线程延迟?如果是这样,我该如何应对?
以下是我如何处理从 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();
}
}
解决加载大量图片卡顿的一些方法
为您的
RecyclerView
设置固定大小,但如果涉及分页,它不会帮助您mRecyclerView.setHasFixedSize(true);
像这样使用滑动覆盖图像的像素
Glide.with(this) .load(YOUR_URL) .apply(new RequestOptions().override(dimenInPx, dimenInPx) .placeholder(R.drawable.placeHolder).error(R.drawable.error_image)) .into(imageview);
将
hardwareAccelerated="true"
属性 添加到清单中的 activity 标签,如<activity ... android:hardwareAccelerated="true" ... />
我认为这可能有所帮助。
尝试使用分页库结合recyclerview
link参考:
Paging Library for Android With Kotlin: Creating Infinite Lists
Android Jetpack: manage infinite lists with RecyclerView and Paging (Google I/O '18)
经过反复试验,我终于弄明白了这个问题。我的第一个错误是没有为 recyclerview 项目发布我的 xml 布局文件,因为这是性能问题的根源。第二个错误是我使用的是 LinearLayout 并将其自己的 layout_width 和 layout_height 属性设置为 75dp,而不是嵌套在 LinearLayout 内部并使用 wrap_content 作为ImageView各自的属性。因此,为了解决性能问题,我做了以下操作:
- 我将
LinearLayout
更改为ConstraintLayout
(我读到它总体上更优化) - 我将 ConstraintLayout 的
layout_width
&layout_height
属性设置为wrap_content
最后 - 我将实际 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>
This answer from Github was very helpful
基本上,如果您设置图像的确切尺寸,Glide 就不必经常计算图像需要多大,从而显着提高性能。这有助于将我的 RecyclerView 从无法使用变为完美,只需在我正在加载的 ImageView 上设置定义的高度和宽度。
相信我。不要像接受的答案中那样使用 ConstraintLayout
。与 LinearLayout
相比,它的性能较低。根据我的测量,用 ConstraintLayout
扩展布局文件平均需要 16 毫秒,用 LinearLayout
扩展布局文件需要 10 毫秒。要有平滑的滚动效果,它必须至少在 16 毫秒内膨胀布局。
在 Glide 示例应用程序 here 中,他们创建了一个 SquareImageView
returns 相同的宽度和高度。
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
<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
,因为它可能会在第一次膨胀 ViewHolder 时造成一些延迟。
另一种选择是预膨胀一些视图并在 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)
}