转载自琼珶和予
RecyclerView 源码分析(三)RecyclerView的缓存机制
RecyclerView的缓存机制
RecyclerView作为一个非常惹人爱的控件,有一部分的功劳归于它优秀的缓存机制。RecyclerView的缓存机制属于RecyclerView的核心部分,同时也是比较难的部分。尽管缓存机制那么难,但是还是不能抵挡得住我们的好奇心。今天我们来看看它的神奇之处。
本文参考资料:
RecyclerView缓存原理,有图有真相
【进阶】RecyclerView源码解析(二)——缓存机制
深入 RecyclerView 源码探究四:回收复用和动画
手摸手第二弹,可视化 RecyclerView 缓存机制
RecyclerView 源码分析(一) - RecyclerView的三大流程
由于本文跟本系列的前两篇文章都有关联,所以为了便于理解,可以去看作者本系列的前两篇文章。
注意,本文所有的代码都来自于27.1.1。
1. 概述
在正式分析源码之前,我先对缓存机制做一个概述,同时也会对一些概念进行统一解释,这些对后面的分析有很大的帮助,因为如果不理解这些概念的话,后面容易看得雨里雾里的。
(1) 四级缓存
首先,我将RecyclerView的缓存分为四级,可能有的人将它分为三级,这些看个人的理解。这里统一说明一下每级缓存的意思。
缓存级别 | 实际变量 | 含义 |
---|---|---|
一级缓存 | mAttachedScrap和mChangedScrap | 这是优先级最高的缓存,RecyclerView在获取ViewHolder时,优先会到这两个缓存来找。其中mAttachedScrap存储的是当前还在屏幕中的ViewHolder,mChangedScrap存储的是数据被更新的ViewHolder, 比如说调用了Adapter的notifyItemChanged方法。可能有人对这两个缓存还是有点疑惑,不要急,待会会详细的解释。 |
二级缓存 | mCachedViews | 默认大小为2,通常用来存储预取的ViewHolder,同时在回收ViewHolder时,也会可能存储一部分的ViewHolder,这部分的ViewHolder通常来说,意义跟一级缓存差不多 。 |
三级缓存 | ViewCacheExtension | 自定义缓存,通常用不到,在本文中先忽略 |
四级缓存 | RecyclerViewPool | 根据ViewType来缓存ViewHolder,每个ViewType的数组大小为5,可以动态的改变 。 |
如上表,统一的解释了每个缓存的含义和作用。在这里,我再来对其中的几个缓存做一个详细的解释。
- mAttachedScrap:上表中说,
它表示存储的是当前还在屏幕中ViewHolder
。实际上是从屏幕上分离出来的ViewHolder,但是又即将添加到屏幕上去的ViewHolder
。比如说,RecyclerView上下滑动,滑出一个新的Item,此时会重新调用LayoutManager的onLayoutChildren方法,从而会将屏幕上所有的ViewHolder先scrap掉(含义就是废弃掉),添加到mAttachedScrap里面去,然后在重新布局每个ItemView时,会从优先mAttachedScrap里面获取
,这样效率就会非常的高。这个过程不会重新onBindViewHolder
。 - mCachedViews:
默认大小为2,不过通常是3,3由默认的大小2 + 预取的个数1
。所以在RecyclerView在首次加载时,mCachedViews的size为3(这里以LinearLayoutManager的垂直布局为例)
。通常来说,可以通过RecyclerView的setItemViewCacheSize方法设置大小,但是这个不包括预取大小;预取大小通过LayoutManager的setItemPrefetchEnabled方法来控制。
(2) ViewHolder的几个状态值
我们在看RecyclerView的源码时,可能到处都能看到调用ViewHolder的isInvalid、isRemoved、isBound、isTmpDetached、isScrap和isUpdated这几个方法
。这里我统一的解释一下。
方法名 | 对应的Flag | 含义或者状态设置的时机 |
---|---|---|
isInvalid | FLAG_INVALID | 表示当前ViewHolder是否已经失效。通常来说,在3种情况下会出现这种情况:1.调用了Adapter的notifyDataSetChanged方法;2. 手动调用RecyclerView的invalidateItemDecorations方法;3. 调用RecyclerView的setAdapter方法或者swapAdapter方法 。 |
isRemoved | FLAG_REMOVED | 表示当前的ViewHolder是否被移除。通常来说,数据源被移除了部分数据,然后调用Adapter的notifyItemRemoved方法 。 |
isBound | FLAG_BOUND | 表示当前ViewHolder是否已经调用了onBindViewHolder。 |
isTmpDetached | FLAG_TMP_DETACHED | 表示当前的ItemView是否从RecyclerView(即父View)detach掉。通常来说有两种情况下会出现这种情况:1.手动了RecyclerView的detachView相关方法;2. 在从mHideViews里面获取ViewHolder,会先detach掉这个ViewHolder关联的ItemView 。这里又多出来一个mHideViews,待会我会详细的解释它是什么。 |
isScrap | 无Flag来表示该状态,用mScrapContainer是否为null来判断 | 表示是否在mAttachedScrap或者mChangedScrap数组里面,进而表示当前ViewHolder是否被废弃。 |
isUpdated | FLAG_UPDATE | 表示当前ViewHolder是否已经更新。通常来说,在3种情况下会出现情况:1.isInvalid方法存在的三种情况;2.调用了Adapter的onBindViewHolder方法;3. 调用了Adapter的notifyItemChanged方法 |
(3) ChildHelper的mHiddenViews
在四级缓存中,我们并没有将mHiddenViews算入其中。因为mHiddenViews只在动画期间才会有元素,当动画结束了,自然就清空了。所以mHiddenViews并不算入4级缓存中。
这里还有一个问题,就是上面在解释mChangedScrap时,也在说,当调用Adapter的notifyItemChanged方法,会将更新了的ViewHolder反放入mChangedScrap数组里面。那到底是放入mChangedScrap还是mHiddenViews呢?同时可能有人对mChangedScrap和mAttachedScrap有疑问,这里我做一个统一的解释:
首先,如果调用了Adapter的notifyItemChanged方法,会重新回调到LayoutManager的onLayoutChildren方法里面,而在onLayoutChildren方法里面,会将屏幕上所有的ViewHolder回收到mAttachedScrap和mChangedScrap。
这个过程就是将ViewHolder分别放到mAttachedScrap和mChangedScrap,而什么条件下放在mAttachedScrap,什么条件放在mChangedScrap,这个就是他们俩的区别。
接下来我们来看一段代码,就能分清mAttachedScrap和mChangedScrap的区别了
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true)