针对RecyclerView的优化整理

整理了RecyclerView可以优化的点,并不是需要都使用,需根据具体情况分析。

了解RecyclerView缓存机制后,可以说RecyclerView性能优化的本质就是针对onCreateViewHolderonBindViewHolder的优化,总结之后分为以下几类。


1.减少onCreateViewHolder调用次数

1.1 两个数据源大部分相似时使用swapAdapter代替setAdapter

rv的setadapter大家都会使用,没什么好说的,但关于swapadapter可能就有些人不太知道了,这两个方法最大的不同之处就在于setadapter会直接清空rv上的所有缓存,而swapadapter会将rv上的holder保存到pool中,google提供swapadapter方法考虑到的一个应用场景应该是两个数据源有很大的相似部分的情况下,直接使用setadapter重置的话会导致原本可以被复用的holder全部被清空,而使用swapadapter来代替setadapter可以充分利用rv的缓存机制,可以说是一种更为明智的选择。

1.2 共用回收池

对于一个页面中的多个RecyclerView,如果使用同一个Adapter,可以使用setRecycledViewPool(pool),共用回收池,
避免来每一个RecyclerView都创建一个回收池,特别是RecyclerView嵌套RecyclerView时候,内部的RecyclerView必定使用的都是同一个Adapter,这个时候就很有必要使用回收池了

1.3 增加RecycledViewPool缓存数量

每个类型默认缓存5个

此方法是拿空间换时间,要充分考虑应用内存问题,根据应用实际使用情况设置大小。


2.减少onCreateViewHolder执行时间

2.1 减少item的过度绘制

减少布局层级,尽量少的布局嵌套,尽量少的控件

2.2 Prefetch预取

如果你使用的是RecyclerView默认的布局管理器,你自动的就得到了这些优化。但是如果你使用嵌套的RecyclerView,或者你自己写LayoutManager,则需要自己实现Prefetch,重写collectAdjacentPrefetchPositions

https://juejin.im/entry/58a3f4f62f301e0069908d8f

https://blog.csdn.net/crazy_everyday_xrp/article/details/70344638


3.减少onBindViewHolder调用次数

3.1 使用局部刷新

可以用一下一些方法,替代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不变,但数据发生变化,可能就不会刷新了。

3.2 使用DiffUtil去局部刷新数据

采用android Support 包下的DiffUtil工具类,它主要是为了配合 RecyclerView 使用,通过比对新、旧两个数据集的差异,生成旧数据到新数据的最小变动,然后对有变动的数据项,进行局部刷新。

https://www.cnblogs.com/plokmju/p/7385136.html

https://zhuanlan.zhihu.com/p/26079803

3.3 视情况使用setItemViewCacheSize(size)来加大RecyclerView缓存数目,用空间换取时间提高流畅度

RecyclerView可以设置自己所需要的ViewHolder缓存数量,默认大小是2。如果对于可能来回滑动的RecyclerView,把CacheViews的缓存数量设置大一些,可以省去bindView的时间,加快布局显示。

此方法是拿空间换时间,要充分考虑应用内存问题,根据应用实际使用情况设置大小。


4.减少onBindViewHolder执行时间

4.1 数据处理与视图绑定分离

onBindViewHolder这个方法是绑定数据,并且是在UI线程,如果在该方法进行耗时操作,将会影响滑动的流畅性。

4.2 有大量图片时,滚动停止加载图片,停止后再去加载图片

https://www.jianshu.com/p/a8621f917407

4.3 使用setHasFixedSize避免requestLayout

如果item的高度固定的话可以设置setHasFixedSize(true),这样RecyclerView在onMeasure阶段可以直接计算出高度,不需要多次计算子ItemView的高度。

setHasFixedSize(true)时如果是通过Adapter的增删改插方法去刷新RecyclerView,那么将不需要requestLayout()。如果是通过notifyDataSetChanged()刷新界面,还是会重新调用requestLayout()

https://www.jianshu.com/p/79c9c70f6502

4.4 不要在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

5.其他

5.1 对于RecyclerView,如果不需要动画,就把item动画取消

默认在开启item动画的情况下会使rv额外处理很多的逻辑判断,notify的增删改操作都会对应相应的item动画效果,所以如果你的应用不需要这些动画效果的话可以直接关闭掉,这样可以在处理增删改操作时大大简化rv的内部逻辑处理。可以通过 ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false); 把默认动画关闭。

5.2 使用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;
    }
};

6.设计优化

6.1 优化解耦 RecyclerView.Adapter

我们在使用 RecyclerView 的时候,总会遇到多项 ItemType 的场景。随着业务复杂度的增加,ItemType 会越变越多,导致代码量越来越多,最终发展为 “上帝类”,需要设计出一种模式,使得增删改一种 ItemType 时的成本降到最低

https://puke3615.github.io/2018/08/26/Android-RecyclerView-Architecture-Design/

https://www.jianshu.com/p/1297d2e4d27a

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章