RecyclerView的功能优化

在咱们谈RecyclerView的功能优化之前,先让咱们回顾一下RecyclerView的缓存机制。

RecyclerView缓存机制

众所周知,RecyclerView拥有四级缓存,它们分别是:

  • Scrap缓存:包含mAttachedScrap和mChangedScrap,又称屏内缓存,不参加滑动时的收回复用,只是用作临时保存的变量。
    • mAttachedScrap:只保存从头布局时从RecyclerView别离的item的无效、未移除、未更新的holder。
    • mChangedScrap:只会担任保存从头布局时产生改变的item的无效、未移除的holder。
  • CacheView缓存:mCachedViews又称离屏缓存,用于保存最新被移除(remove)的ViewHolder,现已和RecyclerView别离的视图,这一级的缓存是有容量限制的,默许最大数量为2。
  • ViewCacheExtension:mViewCacheExtension又称拓宽缓存,为开发者预留的缓存池,开发者能够自己拓宽收回池,一般不会用到。
  • RecycledViewPool:终极的收回缓存池,真实存放着被标识废弃(其他池都不愿意收回)的ViewHolder的缓存池。这里的ViewHolder是现已被抹除数据的,没有任何绑定的痕迹,需求从头绑定数据。

RecyclerView的收回原理

(1)假如是RecyclerView不翻滚状况下缓存(比方删除item)、从头布局时。

  • 把屏幕上的ViewHolder与屏幕别离下来,存放到Scrap中,即产生改动的ViewHolder缓存到mChangedScrap中,不产生改动的ViewHolder存放到mAttachedScrap中。
  • 剩余ViewHolder会按照mCachedViews > RecycledViewPool的优先级缓存到mCachedViews或许RecycledViewPool中。

(2)假如是RecyclerView翻滚状况下缓存(比方滑动列表),在滑动时填充布局。

  • 先移除滑出屏幕的item,第一级缓存mCachedViews优先缓存这些ViewHolder。
  • 由于mCachedViews最大容量为2,当mCachedViews满了以后,会利用先进先出准则,把旧的ViewHolder存放到RecycledViewPool中后移除掉,腾出空间,再将新的ViewHolder添加到mCachedViews中。
  • 最后剩余的ViewHolder都会缓存到终极收回池RecycledViewPool中,它是依据itemType来缓存不同类型的ArrayList,最大容量为5。

RecyclerView的复用原理

当RecyclerView要拿一个复用的ViewHolder时:

  • 假如是预加载,则会先去mChangedScrap中精准查找(分别依据position和id)对应的ViewHolder。
  • 假如没有就再去mAttachedScrap和mCachedViews中精确查找(先position后id)是不是本来的ViewHolder。
  • 假如还没有,则终究去mRecyclerPool找,假如itemType类型匹配对应的ViewHolder,那么回来实例,让它从头绑定数据
  • 假如mRecyclerPool也没有回来ViewHolder才会调用createViewHolder()从头去创立一个。

这里有几点需求注意:

  • 在mChangedScrap、mAttachedScrap、mCachedViews中拿到的ViewHolder都是精准匹配。
  • mAttachedScrap和mCachedViews没有产生改变,是直接运用的。
  • mChangedScrap由于产生了改变,mRecyclerPool由于数据已被抹去,所以都需求调用onBindViewHolder()从头绑定数据才能运用。

缓存机制总结

  • RecyclerView最多能够缓存 N(屏幕最多可显现的item数【Scrap缓存】) + 2 (屏幕外的缓存【CacheView缓存】) + 5*M (M代表M个ViewType,缓存池的缓存【RecycledViewPool】)。
  • RecyclerView实际只要两层缓存可供运用和优化。由于Scrap缓存池不参加翻滚的收回复用,所以CacheView缓存池被称为一级缓存,又由于ViewCacheExtension缓存池是给开发者定义的缓存池,一般不用到,所以RecycledViewPool缓存池被称为二级缓存。

假如想深入了解RecyclerView缓存机制的同学,能够参阅《RecyclerView的收回复用缓存机制详解》 这篇文章。

功能优化方案

依据上面咱们对缓存机制的了解,咱们能够简略得到以下几个大方向:

  • 1.进步ViewHolder的复用,削减ViewHolder的创立和数据绑定作业。【最重要】
  • 2.优化onBindViewHolder办法,削减ViewHolder绑定的时刻。由于ViewHolder可能会进行屡次绑定,所以在onBindViewHolder()尽量只做简略的作业。
  • 3.优化onCreateViewHolder办法,削减ViewHolder创立的时刻。

进步ViewHolder的复用

1.多运用Scrap进行部分更新。

  • (1) 运用notifyItemChangenotifyItemInsertednotifyItemMovednotifyItemRemoved等办法代替notifyDataSetChanged办法。
  • (2) 运用notifyItemChanged(int position, @Nullable Object payload)办法,传入需求改写的内容进行部分增量改写。这个办法一般很少有人知道,详细做法如下:
    • 首要在notify的时候,在payload中传入需求改写的数据,一般运用Bundle作为数据的载体。
    • 然后重写RecyclerView.AdapteronBindViewHolder(@NonNull RecyclerViewHolder holder, int position, @NonNull List<Object> payloads)办法
      @Override
      public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position, @NonNull List<Object> payloads) {
          if (CollectionUtils.isEmpty(payloads)) {
              Logger.e("正在进行全量改写:" + position);
              onBindViewHolder(holder, position);
              return;
          }
          // payloads为非空的状况,进行部分改写
          //取出咱们在getChangePayload()办法回来的bundle
          Bundle payload = WidgetUtils.getChangePayload(payloads);
          if (payload == null) {
              return;
          }
          Logger.e("正在进行增量改写:" + position);
          for (String key : payload.keySet()) {
              if (KEY_SELECT_STATUS.equals(key)) {
                  holder.checked(R.id.scb_select, payload.getBoolean(key));
              }
          }
      }
      

详细运用办法可参阅XUI中的RecyclerView部分增量改写 中的代码。

  • (3) 运用DiffUtilSortedList进行部分增量改写,进步改写效率。和上面讲的传入payload原理相同,这两个是Android默许提供给咱们运用的两个封装类。这里我以DiffUtil举例说明该如何运用。
    • 首要需求完成DiffUtil.Callback的5个笼统办法,详细可参阅DiffUtilCallback.java
    • 然后调用DiffUtil.calculateDiff办法回来比较的成果DiffUtil.DiffResult
    • 最后调用DiffUtil.DiffResultdispatchUpdatesTo办法,传入RecyclerView.Adapter进行数据改写。

详细运用办法可参阅XUI中的DiffUtil部分改写 和 XUI中的SortedList自动数据排序改写 中的代码。

2.合理设置RecyclerViewPool的巨细。假如一屏的item较多,那么RecyclerViewPool的巨细就不能再运用默许的5,可适度增大Pool池的巨细。假如存在RecyclerView中嵌套RecyclerView的状况,能够考虑复用RecyclerViewPool缓存池,削减开支。

3.为RecyclerView设置setHasStableIds为true,并一起重写RecyclerView.Adapter的getItemId办法来给每个Item一个唯一的ID,进步缓存的复用率。

4.视状况运用setItemViewCacheSize(size)来加大CacheView缓存数目,用空间交换时刻进步流通度。对于可能来回滑动的RecyclerView,把CacheViews的缓存数量设置大一些,能够省去ViewHolder绑定的时刻,加速布局显现。

5.当两个数据源大部分类似时,运用swapAdapter代替setAdapter。这是由于setAdapter会直接清空RecyclerView上的所有缓存,但是swapAdapter会将RecyclerView上的ViewHolder保存到pool中,这样当数据源类似时,就能够进步缓存的复用率。

优化onBindViewHolder办法

1.在onBindViewHolder办法中,去除冗余的setOnItemClick等事件。由于直接在onBindViewHolder办法中创立匿名内部类的办法来完成setOnItemClick,会导致在RecyclerView快速滑动时创立很多对象。应当把事件的绑定在ViewHolder创立的时候和对应的rootView进行绑定。

2.数据处理与视图绑定别离,去除onBindViewHolder办法里边的耗时操作,只做朴实的数据绑定操作。当程序走到onBindViewHolder办法时,数据应当是预备齐备的,禁止在onBindViewHolder办法里边进行数据获取的操作。

3.有很多图片时,翻滚时中止加载图片,中止后再去加载图片。

4.对于固定尺寸的item,能够运用setHasFixedSize避免requestLayout

优化onCreateViewHolder办法

1.下降item的布局层级,能够削减界面创立的渲染时刻。

2.Prefetch预取。假如你运用的是嵌套的RecyclerView,或许你自己写LayoutManager,则需求自己完成Prefetch,重写collectAdjacentPrefetchPositions办法。

其他

以上都是针对RecyclerView的缓存机制打开的优化方案,其实还有几种方案可供参阅。

1.取消不需求的item动画。详细的做法是:

((SimpleItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);

2.运用getExtraLayoutSpace为LayoutManager设置更多的预留空间。当RecyclerView的元素比较高,一屏只能显现一个元素的时候,第一次滑动到第二个元素会卡顿,这个时候就需求预留的额定空间,让RecyclerView预加载可重用的缓存。

最后

以上就是RecyclerView功能优化的全部内容,俗话说:百闻不如一见,百见不如一干,大家仍是赶忙着手尝试着开始进行优化吧!

我是xuexiangjys,一枚热爱学习,喜好编程,勤于思考,致力于Android架构研究以及开源项目经历分享的技术up主。获取更多资讯,欢迎微信搜索公众号:【我的Android开源之旅】

本文正在参加「金石方案 . 瓜分6万现金大奖」