美文网首页Android应用开发那些事
RecyclerView 简析之缓存机制及优化

RecyclerView 简析之缓存机制及优化

作者: tandeneck | 来源:发表于2020-05-14 12:26 被阅读0次

    RecyclerView 是用于大量数据展示的控件,相对于传统的 ListView ,更加强大和灵活。

    缓存机制

    RecyclerView 与 ListView 的缓存机制原理大致相似, 滑动的时候,离屏的 ItemView 被回收至缓存,入屏的 ItemView 则会优先从缓存中获取,只是 ListView 与 RecyclerView 的实现细节有差异。

    ListView 缓存机制

    ListView 主要是二级缓存,缓存的对象是 View,ListView 是继承于 AbsListView 的,而 AbsListView 里面有个 mRecycler,用于存储不使用的 view,其将被下次 layout 的时候重新使用,以避免创建新的实例。

        /**
         * The data set used to store unused views that should be reused during the next layout
         * to avoid creating new ones
         */
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769398)
        final RecycleBin mRecycler = new RecycleBin();
    

    RecycleBin 是 AbsListView 的内部类,其作用是通过两级缓存来缓存 view。(RecycleBin 在 layout 的过程中便于 view 重用,RecycleBin 有两级缓存:mActiveViews 和 mScrapViews)。

    • mActiveViews
      第一级缓存,这些 View 是布局过程开始时屏幕上的 view,layout 开始时这个数组被填充,layout 结束,mActiveViews 中的 View 移动到 mScrapView,意义在于快速重用屏幕上可见的列表项 ItemView,而不需要重新 createView 和 bindView。
    • mScrapView
      第二级缓存,mScrapView 是多个 List 组成的数据,数组的长度为 viewTypeCount,每个 List 缓存不同类型 Item 布局的 View,其意义在于缓存离开屏幕的 ItemView,目的是让即将进入屏幕的 itemView 重用,当 mAdapter 被更换时,mScrapViews 则被清空。
    RecyclerView 缓存机制

    同样地,RecyclerView 也有一个类专门来管理缓存,不过与 ListView 不同的是,RecylerView 缓存的是 ViewHolder,而且实现的是四级缓存,如下:

    public final class Recycler {
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    private ArrayList<ViewHolder> mChangedScrap = null;
    
    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
    
    private final List<ViewHolder>
            mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
    
    private RecycledViewPool mRecyclerPool;
    
    private ViewCacheExtension mViewCacheExtension;
    
    • mAttachedScrap
      第一级缓存,相当于 ListView 的 mActiveView,快速重用屏幕上可见的 ViewHolder。
    • mCacheViews
      第二级缓存,如果仍依赖于 RecyclerView(比如已经滑出可视范围,但还没有被移除掉),但已经被标记移除的 ItemView 集合被添加到 mAttachedScrap 中。然后如果 mAttachedScrap 中不再依赖时会被加入到 mCachedViews 中,默认缓存 2 个 ItemView,RecycleView 从这里获取的缓存时,如果数据源不变的情况下,无需重新 bindView。
    • mViewCacheExtension
      第三级缓存,其是一个抽象静态类,用于充当附加的缓存池,当 RecyclerView 从 mCacheViews 找不到需要的 View 时,将会从 ViewCacheExtension 中寻找。不过这个缓存是由开发者维护的,如果没有设置它,则不会启用。通常我们也不会设置它,除非有特殊需求,比如要在调用系统的缓存池之前,返回一个特定的视图,才会用到它。
    • RecycledViewPool
      第四级缓存,最强大的缓存器,代码如下:
    public static class RecycledViewPool {
    
     // 根据 viewType 保存的被废弃的 ViewHolder 集合,以便下次使用
     private SparseArray<ArrayList<ViewHolder>> mScrap = new SparseArray<ArrayList<ViewHolder>>();
      /**
       * 从缓存池移除并返回一个 ViewHolder
       */
      public ViewHolder getRecycledView(int viewType) {
        final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
        if (scrapHeap != null && !scrapHeap.isEmpty()) {
          final int index = scrapHeap.size() - 1;
          final ViewHolder scrap = scrapHeap.get(index);
          scrapHeap.remove(index);
          return scrap;
        }
          return null;
        }
    
      public void putRecycledView(ViewHolder scrap) {
        final int viewType = scrap.getItemViewType();
        final ArrayList scrapHeap = getScrapHeapForType(viewType);
        if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
          return;
        }
        scrap.resetInternal();
        scrapHeap.add(scrap);
      }
    
      /**
       * 根据 viewType 获取对应缓存池
       */
      private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
        ArrayList<ViewHolder> scrap = mScrap.get(viewType);
          if (scrap == null) {
            scrap = new ArrayList<>();
            mScrap.put(viewType, scrap);
              if (mMaxScrap.indexOfKey(viewType) < 0) {
                mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
              }
          }
        return scrap;
      }
    }
    

    顾名思义,它是一个缓存池,实现上,是通过一个默认为 5 大小的 ArrayList 实现的。这一点,同 ListView 的 RecyclerBin 这个类一样。每一个 ArrayList 又都是放在一个 Map 里面的,SparseArray 用两个数组用来替代 Map。
    把所有的 ArrayList 放在一个 Map 里面,这也是 RecyclerView 最大的亮点,这样根据 itemType 来取不同的缓存 Holder,每一个 Holder 都有对应的缓存,而只需要为这些不同 RecyclerView 设置同一个 Pool 就可以了。
    这个可以在 Pool 的 setRecycledViewPool() 方法可以看到注释:

    /**
     * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
     * This can be useful if you have multiple RecyclerViews with adapters that use the same
     * view types, for example if you have several data sets with the same kinds of item views
     * displayed by a {@link android.support.v4.view.ViewPager ViewPager}.
     *
     * @param pool Pool to set. If this parameter is null a new pool will be created and used.
     */
    public void setRecycledViewPool(RecycledViewPool pool) {
        mRecycler.setRecycledViewPool(pool);
    }
    

    RecyclerView 优化

    • 数据处理和视频加载分离
      耗时的数据处理逻辑应该放在异步处理,这样 Adapter 在 notify 改变数据时,ViewHolder 可以操作数据于视图的绑定逻辑。比如:
    mTextView.setText(Html.fromHtml(data).toString());
    

    这里的 Html.fromHtml(data) 方法可能就是比较耗时的,存在多个 TextView 的话耗时会更为严重,这样便会引发掉帧、卡顿,故此时应该在子线程处理。

    • 数据优化
      分页拉取数据时,对拉取下来的远端数据进行缓存,提升二次加载速度;对于新增或者删除数据通过 DiffUtil 来进行局部刷新数据,而不是一味地全局刷新数据。

    • 减少布局层级过渡绘制
      可以通过自定义 View 或者更合理地设置布局来减少层级,移除不必要的背景减少过度绘制。

    • 减少 xml 文件 inflate 时间
      这里的 xml 文件不仅包括 layout 的 xml,还包括 drawable 的 xml,xml 文件 inflate 出 ItemView 时通过耗时的 IO 操作,尤其当 Item 的复用几率很低的情况下,对着 Type 的增多,这种 inflate 带来的损耗时相当大的,此时我们可以用代码去生成布局。

    • 减少 View 对象的创建
      一个稍微复杂的 Item 会包含大量的 View,而大量的 View 的创建也会消耗大量时间,所以要尽可能简化 ItemView;设计 ItemType 时,对多 ViewType 能够共用的部分尽量设计成自定义 View,减少 View 的构造和嵌套。

    • 使用 RecyclerView 的 prefetch 功能

    • 如果 Item 高度是固定的话,可以使用 RecylerView.setHasFixedSize(true),来避免 requestLayout 浪费资源。

    • 滑动过程冲停止数据加载或者图片加载工作。

    • 如果不需要动画,把默认动画关闭来提升效率,动画在 Android 系统中是一个很大的开销。

    • 通过 RecyclerView.setItemViewCacheSize(size);来加大 RecyclerView 的缓存,用空间换时间来提高滚动的流畅性。

    • 如果多个 RecyclerView 的 Adapter 是一样的,比如嵌套的 RecyclerView 存在一样的 Adapter,可以通过设置 RecyclerView.setRecycledViewPool() 方法来共用一个 RecyledViewPool。

    • 通过 getExtraLayoutSpace() 方法来增加 RecyclerView 预留的额外空间(显示范围之外,应额外缓存空间),如下:

    new LinearLayoutManager(this) {
        @Override
        protected int getExtraLayoutSpace(RecyclerView.State state) {
            return size;
        }
    };
    
    • 在 onBindView 的时候只做数据绑定数据工作,不要创建对象。

    相关文章

      网友评论

        本文标题:RecyclerView 简析之缓存机制及优化

        本文链接:https://www.haomeiwen.com/subject/brpznhtx.html