RecyclerView属性中的setHasFixedSize与性能优化
RecyclerView
- 预加载 (加快 recyclerview 的第一屏的加载速度)
我们可以把创建对象从 oncreateviewholder 中提前到外面,用集合进行存储,用
的时候直接从集合中取
- setHasFixedSize (避免 requestLayout 浪费)
如果item的高度固定的话可以设置setHasFixedSize(true),这样RecyclerView在onMeasure阶段可以直接计算出高度,不需要多次计算子ItemView的高度。
setHasFixedSize(true)时如果是通过Adapter的增删改插方法去刷新RecyclerView,那么将不需要requestLayout()。如果是通过notifyDataSetChanged()刷新界面,还是会重新调用requestLayout()
https://www.jianshu.com/p/79c9c70f6502
recyclerView.setHasFixedSize方法什么时候设置为true 什么时候设置为false呢?
setHasFixedSize 为 true,是为了更改 adapter的内容不会改变 它的View的高度和宽度,那么就可以设置为 true来避免不必要的 requestLayout
简单的说:
(1)如果我们使用固定的宽度/高度:
<android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
可以用setHasFixedSize(true)
(2)如果我们不使用固定的宽度/高度:
<android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
应该使用setHasFixedSize(false),因为宽度或高度可以改变我们的大小。
总之,如果高度固定,可以设置 setHasFixedSize(true)来避免 requestLayout 浪费
资源,否则每次更新数据都会重新测量高度当, 布局文件高度或宽度设置为固定值或者 match_parent时 ,可以设置 setHasFixedSize =true
- setItemViewCacheSize (加大RecyclerView缓存)
加大RecyclerView缓存, 比如cacheview大小默认为2,可以设置大点,用空间来换取时间,提高流畅度
RecyclerView可以设置自己所需要的ViewHolder缓存数量,默认大小是2。如果对于可能来回滑动的RecyclerView,把CacheViews的缓存数量设置大一些,可以省去bindView的时间,加快布局显示。
此方法是拿空间换时间,要充分考虑应用内存问题,根据应用实际使用情况设置大小
- setRecycledViewPool (共用回收池)
对于一个页面中的多个RecyclerView,如果使用同一个Adapter,可以使用setRecycledViewPool(pool),共用回收池
避免来每一个RecyclerView都创建一个回收池,特别是RecyclerView嵌套RecyclerView时候,内部的RecyclerView必定使用的都是同一个Adapter,这个时候就很有必要使用回收池了
- recycledViewPool.setMaxRecycledViews (增加RecycledViewPool缓存数量)
每个类型默认缓存5个
此方法是拿空间换时间,要充分考虑应用内存问题,根据应用实际使用情况设置大小
- setSupportsChangeAnimations (简化rv的内部逻辑)
对于RecyclerView,如果不需要动画,就把item动画取消
默认在开启item动画的情况下会使rv额外处理很多的逻辑判断,notify的增删改操作都会对应相应的item动画效果,所以如果你的应用不需要这些动画效果的话可以直接关闭掉,这样可以在处理增删改操作时大大简化rv的内部逻辑处理。可以通过 ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false); 把默认动画关闭
Adapter
- swapAdapter, setAdapter (两个数据源大部分相似替换Adapter)
swapAdapter, setAdapter不同点:
setadapter: 会直接清空rv上的所有缓存
swapadapter: 会将rv上的holder保存到pool中
google提供swapadapter方法考虑到的一个应用场景应该是两个数据源有很大的相似部分的情况下,直接使用setadapter重置的话会导致原本可以被复用的holder全部被清空,而使用swapadapter来代替setadapter可以充分利用rv的缓存机制,可以说是一种更为明智的选择
- 优化解耦 RecyclerView.Adapter (降低增删改ItemType成本)
我们在使用 RecyclerView 的时候,总会遇到多项 ItemType 的场景。随着业务复杂度的增加,ItemType 会越变越多,导致代码量越来越多,最终发展为 “上帝类”,需要设计出一种模式,使得增删改一种 ItemType 时的成本降到最低
https://puke3615.github.io/2018/08/26/Android-RecyclerView-Architecture-Design/
https://www.jianshu.com/p/1297d2e4d27a
LayoutManager
- setInitialItemPrefetchCount (Prefetch预取)
25.1.0 (>=21)及以上使用 Prefetch 功能,也就是预取功能,嵌套时且使用
的是 LinearLayoutManager,子 RecyclerView 可通过 setInitialPrefatchItemCount 设
置预取个数
如果你使用的是RecyclerView默认的布局管理器,你自动的就得到了这些优化。但是如果你使用嵌套的RecyclerView,或者你自己写LayoutManager,则需要自己实现Prefetch,重写collectAdjacentPrefetchPositions
https://juejin.im/entry/58a3f4f62f301e0069908d8f
https://blog.csdn.net/crazy_everyday_xrp/article/details/70344638
- getExtraLayoutSpace (为LayoutManager设置更多的预留空间)
在RecyclerView的元素比较高,一屏只能显示一个元素的时候,第一次滑动到第二个元素会卡顿。
RecyclerView (以及其他基于adapter的view,比如ListView、GridView等)使用了缓存机制重用子 view(即系统只将屏幕可见范围之内的元素保存在内存中,在滚动的时候不断的重用这些内存中已经存在的view,而不是新建view)。
这个机制会导致一个问题,启动应用之后,在屏幕可见范围内,如果只有一张卡片可见,当滚动的时 候,RecyclerView找不到可以重用的view了,它将创建一个新的,因此在滑动到第二个feed的时候就会有一定的延时,但是第二个feed之 后的滚动是流畅的,因为这个时候RecyclerView已经有能重用的view了。
如何解决这个问题呢,其实只需重写getExtraLayoutSpace()方法。根据官方文档的描述 getExtraLayoutSpace将返回LayoutManager应该预留的额外空间(显示范围之外,应该额外缓存的空间)。
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this) {
@Override
protected int getExtraLayoutSpace(RecyclerView.State state) {
return 300;
}
};
ViewHolder
- 减少布局嵌套 (减少onCreateViewHolder时间)
减少item的过度绘制, 减少布局层级,尽量少的布局嵌套,尽量少的控件
- onBindViewHolder
不要在onBindViewHolder中设置点击事件
onBindViewHolder中设置点击事件会导致快速滑动时重复创建很多对象,可以采取复用OnClickListener对象,然后在onBindViewHolder()方法中通过setTag(position) 和getTag() 的方式,来传递点击事件的position给listener。
public class TestAdapter extends RecyclerView.Adapter implements View.OnClickListener{
...
@Override
public void onBindViewHolder(final Holder holder, final int position) {
holder.itemView.setOnClickListener(this);
holder.itemView.setTag(position);
...
}
@Override
public void onClick(View v) {
int position = (Integer) v.getTag();
Log.d("onClick", "testBtn" + String.valueOf(position));
}
}
在ViewHolder中设置方案:https://blog.csdn.net/qq_24956515/article/details/80985773
减少耗时操作
bindViewHolder 方法是在 UI 线程进行的,此方法不能进行耗时操作,不然将会影响滑动流畅性。比如进行日期的格式化
减少调用次数
可以用一下一些方法,替代notifyDataSetChanged,达到局部刷新的目的. notifyDataSetChanged会触发所有item的detached回调再触发onAttached回调
notifyItemChanged(int position)
notifyItemInserted(int position)
notifyItemRemoved(int position)
notifyItemMoved(int fromPosition, int toPosition)
notifyItemRangeChanged(int positionStart, int itemCount)
notifyItemRangeInserted(int positionStart, int itemCount)
notifyItemRangeRemoved(int positionStart, int itemCount)
如果必须用 notifyDataSetChanged(),那么最好设置 mAdapter.setHasStableIds(true),并重写getItemId()来给每个Item一个唯一的ID:
@Override
public long getItemId(int position) {
return mDatas.get(position).hashCode();
}
这样,当我们刷新数据时,RecyclerView就能确认是否数据没有变化,ViewHolder也直接复用,减少重新布局的烦恼。但这个使用的前提是数据的id一定是唯一的。如果id不变,但数据发生变化,可能就不会刷新了
- 有大量图片时,滚动时停止加载图片,停止后再去加载图片
https://www.jianshu.com/p/a8621f917407
DiffUtil.ItemCallback
- diffutil进行局部刷新 (减少全局刷新)
对于新增或删除的时候,可以使用 diffutil 进行局部刷新,少用全局刷新
https://www.cnblogs.com/plokmju/p/7385136.html
https://zhuanlan.zhihu.com/p/26079803
参考文章: https://blog.csdn.net/hxl517116279/article/details/107058425
版权归原作者 mozhimen 所有, 如有侵权,请联系我们删除。