RecyclerView缓存原理,有图有真相

1. RecyclerView缓存机制与性能优化关系

RecyclerView作性能优化要说复杂也复杂,好比说布局优化,缓存,预加载等等。其优化的点不少,在这些看似独立的点之间,其实存在一个枢纽:Adapter。由于全部的ViewHolder的建立和内容的绑定都须要通过Adaper的两个函数onCreateViewHolder和onBindViewHolderandroid

所以咱们性能优化的本质就是要减小这两个函数的调用时间和调用的次数。若是咱们想对RecyclerView作性能优化,必须清楚的了解到咱们的每一步操做背后,onCreateViewHolder和onBindViewHolder调用了多少次。所以,了解RecyclerView的缓存机制是RecyclerView性能优化的基础。缓存

为了理解缓存的应用场景,本文首先会简单介绍一下RecyclerView的绘制原理,而后再分析其缓存实现原理。性能优化

这里写图片描述

2. 绘制原理简述

2.1 假设

为了简化问题,绘制原理介绍提供如下假设:bash

  • RecyclerView
    • 以LinearLayoutManager为例
    • 忽略ItemDecoration
    • 忽略ItemAnimator
    • 忽略Measure过程
    • 假设RecyclerView的width和height是肯定的
  • Recycler
    • 忽略mViewCacheExtension

2.2 绘制过程

(1)类的职责介绍函数

LayoutManager:接管RecyclerView的Measure,Layout,Draw的过程布局

Recycler:缓存池性能

Adapter:ViewHolder的生成器和内容绑定器。fetch

(2)绘制过程简介优化

  1. RecyclerView.requestLayout开始发生绘制,忽略Measure的过程
  2. 在Layout的过程会经过LayoutManager.fill去将RecyclerView填满
  3. LayoutManager.fill会调用LayoutManager.layoutChunk去生成一个具体的ViewHolder
  4. 而后LayoutManager就会调用Recycler.getViewForPosition向Recycler去要ViewHolder
  5. Recycler首先去一级缓存(Cache)里面查找是否命中,若是命中直接返回。若是一级缓存没有找到,则去三级缓存查找,若是三级缓存找到了则调用Adapter.bindViewHolder来绑定内容,而后返回。若是三级缓存没有找到,那么就经过Adapter.createViewHolder建立一个ViewHolder,而后调用Adapter.bindViewHolder绑定其内容,而后返回为Recycler。【参见后文:2. 缓存机制】
  6. 一直重复步骤3-5,知道建立的ViewHolder填满了整个RecyclerView为止。

3. 缓存机制

3.1 源码简析

RecyclerView在Recyler里面实现ViewHolder的缓存,Recycler里面的实现缓存的主要包含如下5个对象:atom

  • ArrayList mAttachedScrap:未与RecyclerView分离的ViewHolder列表,若是仍依赖于 RecyclerView (好比已经滑动出可视范围,但尚未被移除掉),但已经被标记移除的 ItemView 集合会被添加到 mAttachedScrap 中
    • 按照id和position来查找ViewHolder
  • ArrayList mChangedScrap:表示数据已经改变的viewHolder列表,存储 notifXXX 方法时须要改变的 ViewHolder,匹配机制按照position和id进行匹配
  • ArrayList mCachedViews:缓存ViewHolder,主要用于解决RecyclerView滑动抖动时的状况,还有用于保存Prefetch的ViewHoder
    • 最大的数量为:mViewCacheMax = mRequestedCacheMax + extraCache(extraCache是由prefetch的时候计算出来的)
  • ViewCacheExtension mViewCacheExtension:开发者可自定义的一层缓存,是虚拟类ViewCacheExtension的一个实例,开发者可实现方法getViewForPositionAndType(Recycler recycler, int position, int type)来实现本身的缓存。
  • mRecyclerPool ViewHolder缓存池,在有限的mCachedViews中若是存不下ViewHolder时,就会把ViewHolder存入RecyclerViewPool中。
    • 按照Type来查找ViewHolder
    • 每一个Type默认最多缓存5个
public final class Recycler {
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;

    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

    private final List<ViewHolder>
            mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);

    private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
    int mViewCacheMax = DEFAULT_CACHE_SIZE;

    RecycledViewPool mRecyclerPool;

    private ViewCacheExtension mViewCacheExtension;

    static final int DEFAULT_CACHE_SIZE = 2;
复制代码

3.2 缓存机制图解

RecyclerView在设计的时候讲上述5个缓存对象分为了3级。每次建立ViewHolder的时候,会按照优先级依次查询缓存建立ViewHolder。每次讲ViewHolder缓存到Recycler缓存的时候,也会按照优先级依次缓存进去。三级缓存分别是:

  • 一级缓存:返回布局和内容都都有效的ViewHolder
    • 按照position或者id进行匹配
    • 命中一级缓存无需onCreateViewHolder和onBindViewHolder
    • mAttachScrap在adapter.notifyXxx的时候用到
    • mChanedScarp在每次View绘制的时候用到,由于getViewHolderForPosition非调用屡次,后面将
    • mCachedView:用来解决滑动抖动的状况,默认值为2
  • 二级缓存:返回View
    • 按照position和type进行匹配
    • 直接返回View
    • 须要本身继承ViewCacheExtension实现
    • 位置固定,内容不发生改变的状况,好比说Header若是内容固定,就可使用
  • 三级缓存:返回布局有效,内容无效的ViewHolder
    • 按照type进行匹配,每一个type缓存值默认=5
    • layout是有效的,可是内容是无效的
    • 多个RecycleView可共享,可用于多个RecyclerView的优化

3.3 实例讲解

实例解释:

(1)因为ViewCacheExtension在实际使用的时候较少用到,所以本例中忽略二级缓存

(2)mChangedScrap和mAttchScrap是RecyclerView内部控制的缓存,本例暂时忽略。

(3)为了简化问题,暂时不考虑PreFetch的状况

(4)图片解释:

  • RecyclerView包含三部分:已经出屏幕,在屏幕里面,即将进入屏幕,咱们滑动的方向是向上
  • RecyclerView包含三种Type:1,2,3。屏幕里面的都是Type=3
  • 红色的线表明已经出屏幕的ViewHoder与Recycler的交互状况
  • 绿色的线表明,即将进入屏幕的ViewHoder进入屏幕时候,ViewHolder与Recycler的交互状况
  • 紫色的线表明mCacheView进入缓存池的状况

出屏幕时候的状况-mCacheViews未满

  1. 当ViewHolder(position=0,type=1)出屏幕的时候,因为mCacheViews是空的,那么就直接放在mCacheViews里面(从0-N是由老到新)。此时ViewHolder在mCacheViews里面布局和内容都是有效的,所以能够直接复用。
  2. ViewHolder(position=1,type=2)同步骤1

出屏幕时候的状况-mCacheViews已经满

  1. 当ViewHolder(position=2,type=1)出屏幕的时候因为一级缓存mCacheViews已经满了,所以而后移除mCacheViews里面最老的ViewHolder(position=0,type=1)到RecyclePool中,而后将ViewHolder(position=2,type=1)存入mCacheViews。此时被移除到RecyclePool的ViewHolder的内容会被标记为无效,当其复用的时候须要再次经过Adapter.bindViewHolder来绑定内容。
  2. ViewHolder(position=3,type=3)同步骤3

进屏幕时候的状况

  1. 当ViewHolder(position=3,type=3)进入屏幕绘制的时候,因为Recycler的mCacheViews里面找不到position匹配的View,同时RecyclerPool里面找不到type匹配的View,所以,其只能经过adapter.createViewHolder来建立ViewHolder,而后经过adapter.bindViewHolder来绑定内容。
  2. 当ViewHolder(position=11,type=1)进入屏幕的时候,发现ReccylerPool里面能找到type=1的缓存,所以直接从ReccylerPool里面取来使用。因为内容是无效的,所以还须要调用bindViewHolder来绑定布局。同时ViewHolder(position=4,type=3)须要出屏幕,会经历步骤3回收的过程
  3. ViewHolder(position=12,type=3)同步骤6

屏幕往下拉ViewHoder(position=1)进入屏幕的状况

  1. 因为mCacheView里面的有position=1的ViewHolder与之匹配,直接返回。因为内容是有效的,所以无需再次绑定内容
  2. ViewHolder(position=0)同步骤8

4. RecyclerView性能优化方向总结

这里写图片描述
相关文章
相关标签/搜索