目录
一、ListView缓存原理
RecyclerView比ListView多了一个LayoutManager(测量和布局的职责)
RV实际的onLayout和onMeasure里的作用都交给LayoutManager来执行,通过LM来执行相对应的逻辑,这样就提供给应用层可配置的空间。
ListView中一个View对应一个ViewHolder
ListView两级缓存,缓存对象是View
RecyclerView四级缓存,缓存对象是ViewHolder
ListView 两级缓存:
1.activeViews :缓存界面上活跃的View (第一级缓存很少被利用起来,几乎很少发挥作用)
作用:作用几乎很小,低版本的Android 在首次刷新界面的时候可能会重复的onLayout,不同sdk的首次渲染到屏幕上的onLayout次数是不同的,所以一级缓存是复用界面上已有的View,避免首次重复onLayout
2.复用划出界面的View避免重复的绑定(findViewById)
ScrapViews数组列表(二维)
通过ViewType找到被缓存的View,重新加载数据
ListView的RecycleBin对应RecyclerView的Recycler
二、RecyclerView缓存
两种缓存类型,四级缓存
RV中ViewHolder的
两种缓存类型
- Scrap (view):在布局期间进入临时分离状态的子视图。指那些连数据都不需要改变的ViewHolder (第一级缓存)
- Recycle (view):先前用于显示适配器特定位置的数据的视图可以放置在高速缓存中以供稍后重用再次显示相同类型的数据。这可以通过跳过初始布局或构造来显着提高性能。类型相同的ViewHolder (其他级缓存)
四级缓存
各级缓存意义的区别:
一级缓存:意义是为了重新更新界面上已有的UI(与复用无关)
二三四级缓存:复用UI
- 一级缓存:mAttachedScrap 和 mChangedScrap ,用来缓存还在屏幕内的 ViewHolder(实际上不是缓存,只是暂存,就是在一次界面的重新布局的时候暂存View,onLayout结束的时候即使有没复用到的View也会从mAttachedScrap中移除到RecycledViewPool中)
-
- mAttachedScrap 存储的是当前还在屏幕中的 ViewHolder;按照 id 和 position 来查找 ViewHolder
- mChangedScrap 表示数据已经改变的 ViewHolder 列表, 存储 notifyXXX 方法时需要改变的 ViewHolder
一般调用adapter的notifyItemRangeChanged被移除的viewholder会保存到mChangedScrap,其余的notify系列方法 (不包括notifyDataSetChanged)移除的viewholder会被保存到mAttachedScrap中
- 二级缓存:mCachedViews ,用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则移除旧 ViewHolder,这里被复用的View不会触发onBindViewHolder
- 三级缓存(可略):ViewCacheExtension ,开发给用户的自定义扩展缓存,需要用户自己管理 View 的创建和缓存。个人感觉这个拓展脱离了 Adapter.createViewHolder 使用的话会造成 View 创建 与 数据绑定及其它代码太分散,不利于维护,使用场景很少仅做了解
- 四级缓存:RecycledViewPool ,ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 时,就会把 ViewHolder 存入RecyclerViewPool 中。
-
- 按照 Type 来查找 ViewHolder
- 每个 Type 默认最多缓存 5 个
- 可以多个 RecyclerView 共享 RecycledViewPool
1.mAttachedScrap:
这个变量是个存放ViewHolder对象的ArrayList,这一级缓存是没有容量限制的,只要符合条件的我来者不拒,全收了。Scrap,这个就属于Scrap中的一种,这里的数据是不做修改的,不会重新走Adapter的绑定方法。
2.mChangedScrap:
与mAttachedScrap的区别是 他是存放了发生变化了的ViewHolder,使用到了这里的缓存的ViewHolder是要重新走Adapter的绑定方法的。
3.复用
RecyclerView复用子View时侯最后是调用方法 tryGetViewHolderForPositionByDeadline,getViewForPosition也是最终调用这个来查找。
各级缓存的查找顺序如下:
ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) { if (mState.isPreLayout()) { // 0) 预布局从 mChangedScrap 里面去获取 ViewHolder,动画相关 holder = getChangedScrapViewForPosition(position); } if (holder == null) { // 1) 分别从 mAttachedScrap、 mHiddenViews、mCachedViews 获取 ViewHolder // 这个 mHiddenViews 是用来做动画期间的复用 holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); } if (holder == null) { final int type = mAdapter.getItemViewType(offsetPosition); // 2) 如果 Adapter 的 hasStableIds 方法返回为 true // 优先通过 ViewType 和 ItemId 两个条件从 mAttachedScrap 和 mCachedViews 寻找 if (mAdapter.hasStableIds()) { holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); } if (holder == null && mViewCacheExtension != null) { // We are NOT sending the offsetPosition because LayoutManager does not know it. // 3) 从自定义缓存获取,别问,问就是别用 View view = mViewCacheExtension getViewForPositionAndType(this, position, type); holder = getChildViewHolder(view); } } if (holder == null) { // 4) 从 RecycledViewPool 获取 ViewHolder holder = getRecycledViewPool().getRecycledView(type); } if (holder == null) { // 缓存全取过了,没有,那只好 create 一个咯 holder = mAdapter.createViewHolder(RecyclerView.this, type); } // This is very ugly but the only place we can grab this information // 大半夜看到这句注释的时候笑出声!像极了我写出丑代码时的无奈。 // 在这后面有一些刷新 ViewHolder 信息的代码,放这很丑,但又只能放这; 为了能走到这,前面有多少 if (holder == null) }
即大致步骤是:
1. 如果执行了RecyclerView动画的话,尝试根据position从mChangedScrap集合中寻找一个ViewHolder
2. 尝试根据position从scrap集合、hide的view集合、mCacheViews(viewholder一级缓存)中寻找一个ViewHolder
3. 根据LayoutManager的position更新到对应的Adapter的position。(这两个position在大部分情况下都是相等的,不过在子view删除或移动时可能产生不对应的情况)
4. 根据Adapter position,调用Adapter.getItemViewType()来获取ViewType
5. 根据stable id(用来表示ViewHolder的唯一,即使位置变化了)从scrap集合和mCacheViews(一级缓存)中寻找一个ViewHolder
6. 根据position和viewType尝试从用户自定义的mViewCacheExtension中获取一个ViewHolder
7. 根据ViewType尝试从RecyclerViewPool中获取一个ViewHolder
8. 调用mAdapter.createViewHolder()来创建一个ViewHolder
9. 如果需要的话调用mAdapter.bindViewHolder来设置ViewHolder。
10.调整ViewHolder.itemview的布局参数为Recycler.LayoutPrams,并返回Holder
虽然步骤很多,逻辑还是很简单的,即从几个缓存集合中获取ViewHolder,如果实在没有就创建。
4.缓存
缓存操作都是调用Recycler的方法,分别是
recycleViewHolderInternal(mCacheViews 和 RecyclerViewPool)和scrapView (缓存到 scrap)
各级缓存的缓存时机
如果调用了 Adapter 的 notifyXXX 方法,会重新回调到 LayoutManager 的onLayoutChildren 方法里面, 而在 onLayoutChildren 方法里面,会将屏幕上所有的 ViewHolder 回收到 mAttachedScrap 和 mChangedScrap。
三、总结
RecyclerView的优化:
1.把ListView的第一级缓存(不需要更新数据,直接重用的)利用起来,L划出屏幕的不能不重新绑定数据直接重用,R的第二级缓存可以缓存两个划出屏幕不需要重新绑定数据直接用的View
2.支持多个RV公用同一个回收池
3.可以单独的根据ViewType设置容量(某种type的View比较多,缓存容量设置大一点)
4.由于回收池不是索引,viewType不需要连续,可以直接用R.layout来作为type