RecyclerView 源码分析(一):Recycler

前言

RecyclerView 是一个好用又复杂的控件,其功能的高度解耦化,规范化的 ViewHolder 写法,以及对动画的友好支持,都是它与传统 ListView 的区别。

它有几大模块:

  • LayoutManager:控制 item 的布局
  • RecyclerView.Adapter:为 RecyclerView 提供数据
  • ItemDecoration:为 RecyclerView 添加分割线
  • ItemAnimator:控制 item 的动画
  • Recycler:负责回收和提供 View,和 RecyclerView 的复用机制相关

下面就从源码(API 28)角度分析 RecyclerView,RecyclerView 的源码很复杂,很难在一篇文章内讲完,所以打算分几篇来讲,本文是第一篇,将围绕 RecyclerView 的内部类 Recycler 展开分析:

RecyclerView.Recycler

首先看一下它的作用,源码上是这样写的:

A Recycler is responsible for managing scrapped or detached item views for reuse.

意思就是 Recycler 负责管理废弃或被 detached 的 item 视图,以便重复利用。

它有以下几个成员变量:

主要成员变量

1
2
3
4
5
6
7
8
9
10

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

ArrayList<ViewHolder> mChangedScrap = null;

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

RecycledViewPool mRecyclerPool;

private ViewCacheExtension mViewCacheExtension;

这几个成员变量都和 RecyclerView 的缓存相关,如果按照四级缓存的话,它们可以这样划分:

第一级缓存:mAttachedScrap、mChangedScrap

第二级缓存:mCachedViews

第三级缓存:ViewCacheExtension

第四级缓存:RecycledViewPool

后面再介绍 mAttachedScrap、mChangedScrap、mCachedViews 具体存的是哪些 ViewHolder。

现在先了解下 RecycledViewPool 和 ViewCacheExtension这两个类:

RecyclerView.Recycler

首先看一下它的作用,源码上是这样写的:

A Recycler is responsible for managing scrapped or detached item views for reuse.

意思就是 Recycler 负责管理废弃或被 detached 的 item 视图,以便重复利用。

它有以下几个成员变量:

主要成员变量

1
2
3
4
5
6
7
8
9
10

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

ArrayList<ViewHolder> mChangedScrap = null;

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

RecycledViewPool mRecyclerPool;

private ViewCacheExtension mViewCacheExtension;

这几个成员变量都和 RecyclerView 的缓存相关,如果按照四级缓存的话,它们可以这样划分:

第一级缓存:mAttachedScrap、mChangedScrap

第二级缓存:mCachedViews

第三级缓存:ViewCacheExtension

第四级缓存:RecycledViewPool

后面再介绍 mAttachedScrap、mChangedScrap、mCachedViews 具体存的是哪些 ViewHolder。

现在先了解下 RecycledViewPool 和 ViewCacheExtension这两个类:

RecycledViewPool

继续先看官方注释:

RecycledViewPool lets you share Views between multiple RecyclerViews.

RecycledViewPool 用于在多个 RecyclerView 间共享 View。

在使用时,只需创建 RecycledViewPool 实例,然后调用 RecyclerView 的 setRecycledViewPool(RecycledViewPool) 方法即可。

RecycledViewPool 存储在 Recycler 中,通过 Recycler 存取。

成员变量

RecycledViewPool 有一个重要的成员变量:

1
2
// SparseArray 类似于 key 为 int 类型 的 HashMap
SparseArray<ScrapData> mScrap = new SparseArray<>();

其中 ScrapData 的定义如下:

1
2
3
4
5
6
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP; // 5
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}

mScrap 是一个 <int, ScrapData> 的映射,其中 int 代表了 viewType,ScrapData 则存储了一个 ViewHolder 集合。

主要方法

getScrapDataForType

1
2
3
4
5
6
7
8
private ScrapData getScrapDataForType(int viewType) {
ScrapData scrapData = mScrap.get(viewType);
if (scrapData == null) {
scrapData = new ScrapData();
mScrap.put(viewType, scrapData);
}
return scrapData;
}

该方法根据 viewType 获取相应的 ScrapData,如果该 viewType 还没有绑定 ScrapData,就新创建一个 ScrapData 并绑定到该 viewType。

setMaxRecycledViews

1
2
3
4
5
6
7
8
9
public void setMaxRecycledViews(int viewType, int max) {
ScrapData scrapData = getScrapDataForType(viewType);
scrapData.mMaxScrap = max;
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
// 从后面开始删除,直到满足新的容量
while (scrapHeap.size() > max) {
scrapHeap.remove(scrapHeap.size() - 1);
}
}

该方法可以设置相应 viewType 的 View 容量,超出容量时,从后面开始删除,直到满足新的容量。

getRecycledView

1
2
3
4
5
6
7
8
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}

该方法根据 viewType 获取一个 ViewHolder,获取到的 ViewHolder 将会被移除出 Scrap 堆。获取不到则返回 null。

putRecycledView

1
2
3
4
5
6
7
8
9
10
11
12
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
// 容量已满,不再添加
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
// 重置 ViewHolder,例如清空 flags
scrap.resetInternal();
// 将该 ViewHolder 添加到对应 viewType 的 集合中缓存起来
scrapHeap.add(scrap);
}

该方法也很好理解,根据 ViewHolder 的 viewType 放入 RecycledViewPool 的相应集合中,如果集合已满,不再添加。

接下来看另一个类:

ViewCacheExtension

ViewCacheExtension 是一个由开发者控制的 View 缓存帮助类,其定义如下:

1
2
3
4
5
6
7
8
9
public abstract static class ViewCacheExtension {

/**
* Returns a View that can be binded to the given Adapter position.
*/
@Nullable
public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
int type);
}

开发者可以实现这个抽象类,通过调用 RecyclerView 的 setViewCacheExtension(ViewCacheExtension) 方法设置,最终将 ViewCacheExtension 存储在 Recycler 中。

当调用 Recycler 的 getViewForPosition 方法时,如果 attached scrap 和 已经缓存都没有找到合适的 View,就会调用 ViewCacheExtension 的 getViewForPositionAndType 方法来获取 View。

需要注意的是,Recycler 不会对这个类做任何缓存处理,是否需要缓存 View 由开发者自己控制。

主要方法

看完这两个类,现在回到 Recycler 中,看一下 Rcycler 的主要方法:

getViewForPosition

getViewForPosition 方法比较重要,用于获取某个位置需要展示的 View,如下:

1
2
3
4
5
6
7
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}

View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

继续看 tryGetViewHolderForPositionByDeadline 方法,该方法会依次从几个缓存中获取,分别来看一下:

1
2
3
4
5
6
       // 如果是处于预布局阶段(先简单理解为执行 dispatchLayoutStep1 方法)
// (其实下面方法要返回 ture 还需要开启“预处理动画”,这跟动画有关,先不多说)
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}

第一步,从 mChangedScrap 中获取,获取不到就返回 null。

如果 holder 还是为 null,执行下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
// 回收无效的 ViewHolder
// ...
} else {
fromScrapOrHiddenOrCache = true;
}
}
}

第二步,根据 position 依次从 mAttachedScrap、mHiddenViews(存储在 ChildHelper 类)、mCachedViews 中获取缓存的 ViewHolder。

可以从 mHiddenViews 获取到缓存的话,就将其从 mHiddenViews 移除并添加到 Scrap 缓存(根据情况添加到 mAttachedScrap 或 mChangedScrap)。可以从 mCacheViews 中获取到缓存的话,就将其从 mCacheViews 移除。

获取到后,发现无效的话,将对获取到的 ViewHolder 进行清理并回收(放入 mCachedViews 或 RecycledViewPool)。

获取不到,就继续往下执行:

1
2
3
4
5
6
7
8
9
10
       // 默认返回 false,可通过 Adapter.setHasStableIds 方法设置该值
if (mAdapter.hasStableIds()) {
// 根据 id 依次在 mAttachedScrap、mCachedViews 中获取缓存
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}

第三步,根据 id 依次从 mAttachedScrap、mCachedViews 中获取缓存,还没有获取到就继续往下:

1
2
3
4
5
6
7
8
9
10
11
// 如果用户设置了 ViewCacheExtension
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
// ...
}
}

第四步,从用户设置的 ViewCacheExtension 中获取缓存,没有获取到就继续往下:

1
2
3
4
if (holder == null) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
// ...
}

第五步,根据 viewType 从 RecycledViewPool 中得到缓存。

RecycledViewPool 已经是最后一级缓存了,如果这里也没有获取到,只能通过 Adapter 的 createViewHolder 方法创建一个 ViewHolder:

1
2
3
4
5
6
if (holder == null) {

holder = mAdapter.createViewHolder(RecyclerView.this, type);

// ...
}

最后小结一下获取某个位置的 View 的过程:

  1. 先后根据 position 或 id 从 mChangedScrap 中获取缓存
  2. 根据 position 依次从 mAttachedScrap、mHiddenViews(存储在 ChildHelper 类)、mCachedViews 中获取缓存
  3. 根据 id 依次从 mAttachedScrap、mCachedViews 中获取缓存
  4. 从用户设置的 ViewCacheExtension 中获取缓存
  5. 从 RecycledViewPool 中得到缓存的废弃 ViewHolder
  6. 通过 Adapter 的 createViewHolder 方法创建一个 ViewHolder

另外,光有视图还不够,还要为视图设置数据,所以后面还有这样一段代码:

1
2
3
4
5
6
// ViewHolder 还未进行绑定操作、带有 FLAG_UPDATE 或 FLAG_INVALID 标记
else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
// ...

bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}

tryBindViewHolderByDeadline 方法最后会调用 Adapter 的 onBindViewHolder 方法,我们平时就是在该方法上给视图设置数据的。

recycleView

既然叫 Recycler,那肯定要做回收工作了,recycleView 方法就完成了这些工作,下面看一下该方法的实现:

1
2
3
4
5
public void recycleView(@NonNull View view) {
ViewHolder holder = getChildViewHolderInt(view);
// ...
recycleViewHolderInternal(holder);
}

继续看 recycleViewHolderInternal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
   void recycleViewHolderInternal(ViewHolder holder) {
// ...

if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {

int cachedViewSize = mCachedViews.size();
// 若 CacheViews 达到最大容量(2),将最老的缓存从 CacheViews 移除,并添加到 RecycledViewPool 中
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}

// ...

// 将 View 缓存到 mCachedViews 中
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
// 没有添加到 mCachedViews 的话,就添加到 RecycledViewPool 中
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}

// ...
}

可以看到,回收过程主要涉及到两层缓存,第一层缓存是 CacheViews,在添加时,如果发现原来的 CacheViews 已经达到最大容量,就将最老的缓存从 CacheViews 移除,并添加到 RecycledViewPool。第二层缓存是 RecycledViewPool,如果不能添加到 mCacheViews,就会添加到 RecycledViewPool 中。

补充

mChangedScrap 和 mAttachedScrap 中的 View 从何而来

从前面可以得知,在执行 Recycler 的 recycleView 方法时,会将回收的 View 缓存到 mCahceViews 或 recycledViewPool 中,那么另外两个 Scrap 缓存(mChangedScrap 和 mAttachedScrap)中的 View 是何时添加进来的呢?

无论是 mAttachedScrap 还是 mChangedScrap ,它们获得 View 的途径都只有一个,那就是通过 Recycler 的 scrapView 方法。先看下该方法:

Recycler#scrapView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
   void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);

// 满足这几个条件中的一个就可以进入 if 循环,有机会将 View 缓存到 mAttachedScrap
// 1. ViewHolder 设置了 FLAG_REMOVED 或 FLAG_INVALID
// 2. ViewHolder 没有设置 FLAG_UPDATE
// 3. 没有设置动画或者动画可以重用该 ViewHolder
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());
}
// 给 ViewHolder 绑定 Recycler
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
}
// 不满足上述任意一个条件时,将 View 缓存到 mChangedScrap 中
else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}

该方法通过判断 ViewHolder 的 flag 以及是否设置 ItemAnimator 等,决定将 View 缓存到 mAttachedScrap 还是 mChangedScrap。

那么该方法在何时调用呢?有两种情况:

  1. 以 LinearLayoutManager 为例,在它的 onLayoutChildren 方法中,会调用
    1
    detachAndScrapAttachedViews(recycler);

该方法定义在 RecyclerView 的 LayoutManager 中,它继续调用 scrapOrRecycleView 方法,如果在该方法符合条件就调用 Recycler 的 scrapView 方法。

  1. 通过 mHiddenViews 获取到缓存时,也会调用 scrapView 方法。

场景分析

下面就根据一些场景来分析下 Recycler 是如何进行回收和复用的。

第一次 layout

由于这里不是专门分析 layout 过程的,就不从 onLayout 开始说了,中间的过程省略掉,它最终会调用到 LayoutManager 的 onLayoutChildren,这里以 LinearLayoutManager 为例:

LinearLayoutManager#onLayoutChildren

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
   @Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// ...

// 找到锚点(具体过程等到分析 layout 时再说)

// (1)
detachAndScrapAttachedViews(recycler);

if (mAnchorInfo.mLayoutFromEnd) {
// ...
} else {

// (2)
fill(recycler, mLayoutState, state, false);

// ...
}

// ...
}

首先看(1)处,detachAndScrapAttachedViews 方法会根据情况将子 View 回收到相应缓存,具体过程之后再看,由于现在是第一次 layout,RecyclerView 中没有子 View,所以现在该方法没啥用。

接下来看(2)处,这里的 fill 方法比较重要,它的作用是填充布局。看一下该方法:

LinearLayoutManager#fill

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {

// 进行 layout 时 layoutState.mScrollingOffset 的值被设置为
// LayoutState.SCROLLING_OFFSET_NaN,不会进入此 if 块
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// ...
recycleByLayoutState(recycler, layoutState);
}

// 需要填充的空间
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
// 还有需要填充的空间并且 item 数未满
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
// ...

// (1)
layoutChunk(recycler, state, layoutState, layoutChunkResult);

// 计算剩余空间

// 同上,在 layout 时不会进入 if 块中
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// ...
recycleByLayoutState(recycler, layoutState);
}

// ...
}
}

主要看(1)处的 layoutChunk 方法,只要还有需要填充的空间,就会不断调用该方法:

LinearLayoutManager#layoutChunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// (1)
View view = layoutState.next(recycler);

// ...

// 默认情况下,layoutState.mScrapList 等于 null
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
// (2)
addView(view);
} else {
addView(view, 0);
}
} else {
// ...
}
}

(2)处的 addView 方法就不多说了,该方法将得到的子 View 添加到 RecyclerView 中。主要看(1)处,看看子 View 从何而来:

1
2
3
4
5
6
7
View next(RecyclerView.Recycler recycler) {
// ...

final View view = recycler.getViewForPosition(mCurrentPosition);

return view;
}

这个方法是不是很熟悉呢?没错,它就是之前分析的 Recycler 的 getViewForPosition 方法。

不过由于现在没有任何缓存,所以第一次 layout 的时候是通过 Adapter 的 createViewHolder 来创建子 View的,并且没有添加任何缓存。

更新列表

更新列表可以使用 Adapter 的一系列 notify 方法,这里分析其中两个方法:notifyDataSetChanged 和 notifyItemChanged(int)。

Adapter#notifyDataSetChanged

该方法最终调用了 RecyclerViewDataObserver 的 onChanged 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   @Override
public void onChanged() {
// ...

// 该方法主要做了这两件事
// 1. 给所有 ViewHolder 添加了 FLAG_UPDATE 和 FLAG_INVALID
// 2. 默认情况下(mHasStableIds 为 false)清空 CacheViews
processDataSetCompletelyChanged(true);

if (!mAdapterHelper.hasPendingUpdates()) {
// 进行视图重绘
requestLayout();
}
}

该方法会进行视图重绘,又来到了 layout 过程,继续以 LinearLayoutManager 为例,从它的 onLayoutChildren 方法看起,由于分析第一次 layout 时已经看过一遍了,这次主要看下不同之处:

1
2
3
4
5
6
7
8
  @Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// ...

detachAndScrapAttachedViews(recycler);

// ...
}

主要区别在于 detachAndScrapAttachedViews 方法,这次它开始起作用了,该方法在 RecyclerView 的 LayoutManager 中定义,看下它的实现:

LayoutManager#detachAndScrapAttachedViews

1
2
3
4
5
6
7
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}

由于不是第一次 layout,RecyclerView 这时已经有子 View 了,该方法遍历子 View,调用 scrapOrRecycleView 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
// 不能回收添加了 FLAG_IGNORE 标记的 ViewHolder
// 可通过 LayoutManager 的 ignoreView 为相应的 View 添加该标记
if (viewHolder.shouldIgnore()) {
return;
}
// 这些条件都满足,进入 if 块
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
// ...
}
}

这里将子 View 移除并通过 Recycler 的 recycleViewHolderInternal 方法进行回收:

Recycler#recycleViewHolderInternal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
      void recycleViewHolderInternal(ViewHolder holder) {
// ...
boolean cached = false;
boolean recycled = false;

if (forceRecycle || holder.isRecyclable()) {
// 由于此时的 ViewHolder 有 FLAG_INVALID 标记,不会进入此 if 块
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
//...
}
// cached 仍为 false,进入此 if 块
if (!cached) {
// 通过 RecycledViewPool 的 putRecycledView 方法缓存该 ViewHolder
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}

// ...
}

最终被移除的子 View 缓存到了 RecycledViewPool 中。

后面在调用 fill 方法进行布局填充时,就可以从 RecycledViewPool 中拿取缓存的 View。

Adapter#notifyItemChanged

该方法传入一个 int 参数,表示要数据有更新的 item 的 position。

1
2
3
public final void notifyItemChanged(int position) {
mObservable.notifyItemRangeChanged(position, 1);
}

最终调用 RecyclerViewDataObserver 的 onItemRangeChanged 方法:

1
2
3
4
5
6
7
8
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
// 会在 mAdapterHelper 中创建一个 UpdateOp,将信息保存起来
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
// 如果可以进行更新操作,执行该方法
triggerUpdateProcessor();
}
}

继续看 triggerUpdateProcessor 方法:

1
2
3
4
5
6
7
8
9
void triggerUpdateProcessor() {
// 判断条件默认为 false,执行 else 块
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
// ...
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}

在保存了一些信息后,还是进行视图重绘。来到了 layout 过程后,还是以 LinearLayoutManager 为例,这次先看下布局过程的 step1,也就是 dispatchLayoutStep1 方法:

RecyclerView#dispatchLayoutStep1

1
2
3
4
5
6
7
private void dispatchLayoutStep1() {
// ...

processAdapterUpdatesAndSetAnimationFlags();

// ...
}

主要看 processAdapterUpdatesAndSetAnimationFlags 方法,从名字也可以看出,它负责更新 adapter 的信息:

1
2
3
4
5
6
7
8
9
10
11
   private void processAdapterUpdatesAndSetAnimationFlags() {
// ...

if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
mAdapterHelper.consumeUpdatesInOnePass();
}

// ...
}

这里借助了 mAdapterHelper,它最终又通过接口回调(回调了 markViewHoldersUpdated 方法)调用了 RecyclerView 的 viewRangeUpdate 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
// ...

for (int i = 0; i < childCount; i++) {
// ...

if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
// (1)
holder.addFlags(ViewHolder.FLAG_UPDATE);
// ...
}
}
}

该方法就是遍历所有子 View,找到所有发生了改变的子 View,进行相关操作。这里重点看注释(1),为改变的 ViewHolder 添加了 FLAG_UPDATE 标记。先记住这点,在后面会用到。

接下来看 onLayoutChildren 方法,和 notifyDataSetChanged 一样,主要的不同之处也是在于 detachAndScrapAttachedViews 方法,该方法遍历子 View,调用 scrapOrRecycleView 方法,下面看一下该方法:

LayoutManager#scrapOrRecycleView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
// ...

// 这次 ViewHolder 没有添加 FLAG_INVALID 标记,进入 else 块
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
// ...
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}

这里就和 notifyDataSetChanged 时不一样了,由于在视图重绘前没有给 ViewHolder 添加 FLAG_INVALID 标记,这次进入的是 else 块。

首先将 View 从 RecyclerView 中 detach 掉(而不是 remove 掉)。然后在回收时,调用的是 Recycler 的 scrapView 方法。该方法在前面也分析过了,这里再看一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
   void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);

// 满足这几个条件中的一个就可以进入 if 循环
// 1. ViewHolder 设置了 FLAG_REMOVED 或 FLAG_INVALID
// 2. ViewHolder 没有设置 FLAG_UPDATE
// 3. 没有设置动画或者动画可以重用该 ViewHolder
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
// ...

mAttachedScrap.add(holder);
}
// 不满足上述任意一个条件时,将 View 缓存到 mChangedScrap 中
else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}

重点看判断里面的条件 2,从前面的分析可以得知,对于发生改变的 ViewHolder,给它设置了 FLAG_UPDATE,所以它现在三个条件都不满足,进入 else 块,而对于其他的 ViewHolder,由于没有设置 FLAG_UPDATE,所以满足条件 2,进入 if 循环。

所以通过 notifyItemChanged 方法更新列表时,发生了改变的子 View 将被缓存到 ChangedScrap 中,而没有发生改变的子 View 则缓存到 AttachedScrap 中,之后通过填充布局的时候对于不同 item 就可以从相应的 Scrap 缓存中得到子 View。

另外,Scrap 缓存只作用于布局阶段,在 layout 的 step3 中将会清空 mAttachedScrap 和 mChangedScrap。

其实还有一个常见的场景是滑动操作,滑动出屏幕的子 View 将会缓存到 mCachedView,不过这里就不详细说了,在之后会在其他文章专门分析滑动这块。

后记

本文围绕 Recycler 展开叙述,重点是要通过它的几个成员变量了解它的缓存机制,四级缓存分别是什么,是在何时调用的,各自起到的作用,不同场景下使用哪种缓存等。

Recycler 和 LayoutManager 的布局以及动画都有联系,例如 LayoutManager 负责布局,它决定获取子 View 和回收子 View 的时机,具体的工作就交由 Recycler 负责。这些会在之后对 RecyclerView 的其他方面作分析时进行更详细的说明。

参考

-------------    本文到此结束  感谢您的阅读    -------------
0%