0 前言
RecyclerView 是 Android 中一个非常重要的组件,用于灵活、高效的展示大量数据集合。本文将从源码对RecycleView进行分析其回收复用机制。
1 RecyclerView 回收复用机制初体验:从日志分析开始
我们先对 RecyclerView
我们试着对RecyclerView的onCreateViewHolder和onBindViewHolder方法添加Log日志,查看调用的时机。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
...
Log.d("ViewHolder","onCreateViewHolder:${itemCount}")
return RVViewHolder(view);
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
...
Log.d("ViewHolder", "onBindViewHolder:$position")
}
一开始没有滑动的时候:
发现
onCreateViewHolder和onBindViewHolder都有被调用。
当我们向上划动的时候:
发现一开始
onCreateViewHolder和onBindViewHolder都有被调用,后来只有onBindViewHolder被调用。
当我们向下滑动的时候:
发现只有
onBindViewHolder被调用。
我们可以看出,不论是上划还是下滑,除了一开始滑动时需要调用 onCreateViewHolder 外,之后的滑动仅需要调用 onBindViewHolder 方法。这就是 RecyclerView 的四级缓存的作用:
2 初步了解 Recycler
Recycle 是 RecyclerView 的一个内部类,是 RecyclerView 缓存复用机制的核心。我们简单了解一下 Recycler 的一部分成员变量:
public final class Recycler {
//屏幕内缓存
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); // 未变更的屏幕内 ViewHolder
ArrayList<ViewHolder> mChangedScrap = null; // 数据变更的屏幕内 ViewHolder(懒加载)
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); // 离屏缓存(最近使用的 ViewHolder)
private final List<ViewHolder> mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); // 不可变视图,防止外部修改
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; // 请求的缓存最大值(用户设置)
int mViewCacheMax = DEFAULT_CACHE_SIZE; // 实际生效的缓存最大值(可能被调整)
RecycledViewPool mRecyclerPool; // 共享缓存池(长期离屏的 ViewHolder)
private ViewCacheExtension mViewCacheExtension; // 开发者自定义缓存扩展
static final int DEFAULT_CACHE_SIZE = 2; // 默认离屏缓存容量
}
从 Recycler 的成员变量中,我们可以看出 RecyclerView 的四级缓存分别是:
- Scrap 缓存(屏幕内缓存):
mChangedScrap与mAttachedScrap,用来缓存还在屏幕内的 ViewHolder。 - CachedViews(离屏缓存):
mCachedViews,用来缓存移出屏幕之外的 ViewHolder。 - 自定义缓存:
mViewCacheExtension,开发者自定义缓存扩展。 - 共享缓存池:
mRecyclerPool,ViewHolder 缓存池。
3 源码解析
3.1 onTouchEvent 切入
我们知道 RecyclerView 会对移出屏幕的 ViewHolder 回收,新进入屏幕的item将会对缓存的 ViewHolder 复用。因此我们可以从触摸方法中开始找切入点,首先先看一下 onTouchEvent 方法中处理滑动的调用流程:
/** RecyclerView.java **/
public boolean onTouchEvent(MotionEvent e) {
// ...
// mLayout 即为 LayoutManager
if (mLayout == null) {
return false;
}
// ...
switch (action) {
// ...
case MotionEvent.ACTION_MOVE: {
// ...
// 根据 Down 事件中保存的信息计算出滑动距离
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
// 滑动前先进行方向判断
if (mScrollState != SCROLL_STATE_DRAGGING) {
boolean startScroll = false;
if (canScrollHorizontally){
if (dx > 0) {
dx = Math.max(0, dx - mTouchSlop);
} else {
dx = Math.min(0, dx + mTouchSlop);
}
if (dx != 0) {
startScroll = true;
}
}
// canScrollVertically,与横向类似
if (startScroll) { // 标记开始滑动
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
// ...
// scrollByInternal 处理滑动的方法
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
e, TYPE_TOUCH)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
// ...
}
}
// ...
}
boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
// ...
if (mAdapter != null) {
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
// scrollStep 方法中计算出了滑动消耗的距离,并且赋值给了 mReusableIntPair
scrollStep(x, y, mReusableIntPair);
consumedX = mReusableIntPair[0];
consumedY = mReusableIntPair[1];
unconsumedX = x - consumedX;
unconsumedY = y - consumedY;
}
// ...
}
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
// ...
int consumedX = 0;
int consumedY = 0;
// 不管是水平还是垂直滑动 最终都调用到了 LayoutManager 的 scrollXXXBy 方法中
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
}
//...
if (consumed != null) {
consumed[0] = consumedX;
consumed[1] = consumedY;
}
}
由于 RecyclerView 的滑动最终是交给 LayoutManager 去处理,我们以 LinearLayoutManager 的 scrollHorizontallyBy 为例( scrollVerticallyBy 类似),进去后看到:
/** LinearLayoutManager.java **/
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == VERTICAL) {
return 0;
}
return scrollBy(dx, recycler, state);
}
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
// ...
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
// ...
}
// 【重要】
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// ..
// 回收
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
// ...
// 复用
layoutChunk(recycler, state, layoutState, layoutChunkResult);
// ...
}
// ...
}
在 RecyclerView 滑动时回收和复用操作会同时触发,有移除屏幕的 ViewHolder 就需要有新的 ViewHolder 来填补。 在这里我们分为回收路线和复用路线,分别来看。
3.2 回收路线
/** LinearLayoutManager **/
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
// ...
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
// 回收头部
recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
} else {
// 回收尾部,与头部类似
recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
}
}
// 以 recycleViewsFromEnd 为例
private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset,
int noRecycleSpace) {
final int childCount = getChildCount();
// ...
if (mShouldReverseLayout) {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedStart(child) < limit
|| mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
// stop here
recycleChildren(recycler, 0, i);
return;
}
}
} else {
for (int i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedStart(child) < limit
|| mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
// stop here
recycleChildren(recycler, childCount - 1, i);
return;
}
}
}
}
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
// ...
if (endIndex > startIndex) {
for (int i = endIndex - 1; i >= startIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
} else {
for (int i = startIndex; i > endIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
}
}
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
final View view = getChildAt(index);
removeViewAt(index);
// 让 RecycleView 回收
recycler.recycleView(view);
}
此时我们回到 RecyclerView 继续。
/** RecyclerView.java */
public void recycleView(@NonNull View view) {
ViewHolder holder = getChildViewHolderInt(view);
// ...
recycleViewHolderInternal(holder);
// ...
}
void recycleViewHolderInternal(ViewHolder holder) {
// ...
// 强制回收判断
final boolean transientStatePreventsRecycling = holder.doesTransientStatePreventRecycling();
@SuppressWarnings("unchecked")
final boolean forceRecycle = mAdapter != null
&& transientStatePreventsRecycling
&& mAdapter.onFailedToRecycleView(holder);
boolean cached = false;
boolean recycled = false;
// ...
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// 缓存已满时,淘汰最旧的 ViewHolder
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0); //移出最旧的ViewHolder
cachedViewSize--;
}
// 添加到 mCachedViews 缓存
int targetCacheIndex = cachedViewSize;
// ...
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
// 如果没有添加到 mCachedView 中则直接加入 RecycledViewPool 中
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
// ...
}
// ...
}
void recycleCachedViewAt(int cachedViewIndex) {
// 把需要操作的 ViewHolder 拿出来放到 RecycleViewPool
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
View itemView = holder.itemView;
// ...
if (dispatchRecycled) {
// 通知监听器 ViewHolder 已回收
dispatchViewRecycled(holder);
}
holder.mOwnerRecyclerView = null;
// 存入 RecycledViewPool
getRecycledViewPool().putRecycledView(holder);
}
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;
}
// ...
scrap.resetInternal();
scrapHeap.add(scrap);
}
小结一下:
- 当 Item 完全离屏时,LinearLayoutManager 通过
recycleByLayoutState确定回收范围,调用recycler.recycleView(view),此时 ViewHolder 会被自动从从mAttachedScrap/mChangedScrap移除。 - 回收的 ViewHolder 如果存在于一级缓存
mAttachedScrap、mChangedScrap中,则自动从中移除; - 将 ViewHolder 加入二级缓存
mCacheViews中,如果容量超出,则移除 index 为 0 的 ViewHolder,并且将其添加到 RecyclerViewPool 中(原来在mCacheViews的 index 值会减一),mCachedViews中的 ViewHolder 不重置状态; - 如果 ViewHolder 在第 2 步中没有成功添加到
mCacheViews中则重置 ViewHolder 装填并添加进 RecyclerViewPool 缓存,且按照类型分类存储; - RecyclerViewPool 中会根据
ItemViewType获取对应 ViewHolder 的 ArrayList 容器(如果还没初始化则直接 new 一个)判断容量后添加进去,添加前会清除 ViewHolder 的一些状态信息。每个类型的缓存独立,容量不足时会丢弃新的 ViewHolder。
无论存入哪级缓存,都会通过 dispatchViewRecycled(holder) 通知监听器,开发者可在此释放资源。
3.3 复用路线
接下来看看复用部分layoutChunk的代码:
/** LinearLayoutManager **/
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
// ...
}
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
// 最终通过 recycler 的 getViewForPosition 方法获取到了 View
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
回到 Recycler 里的 getViewForPosition
/** RecyclerView.java **/
@NonNull
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
// 【核心】
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
// ...
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
// 一级缓存
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 二级缓存
// 1) Find by position from scrap/hidden list/cache
// 通过 position 寻找
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
// ...
}
if (holder == null) {
// ...
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
// 二级缓存,通过 Id 寻找
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.
// 三级缓存,自定义缓存
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
// ...
}
}
if (holder == null) { // fallback to pool
// ...
// 四级缓存,缓存池
holder = getRecycledViewPool().getRecycledView(type);
// ...
}
if (holder == null) {
// ...
// 没有缓存,创建新的ViewHolder
holder = mAdapter.createViewHolder(RecyclerView.this, type);
// 预加载机制
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
mRecyclerPool.factorInCreateTime(type, end - start);
// ...
}
}
// 数据绑定部分
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
// 预布局
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
// ...
// 未绑定、需要更新或无效时,执行数据绑定
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
//布局参数处理
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
return holder;
}
我们从 tryGetViewHolderForPositionByDeadline 方法中可以看到,每个 if 都会判断 holder 是否已经拿到并复用,如果已经拿到,则后面的 if 语句都进不去,运行到后续并返回 holder ,否则 holder == null,则进行下一层的从缓存中复用。
总结一下四级缓存的复用流程:
一级缓存(mAttachedScrap → mChangedScrap) → 二级缓存(mCachedViews) → 稳定ID查找(在Scrap/CachedViews中) → 三级缓存(自定义) → 四级缓存(RecycledViewPool) → 创建新实例
- 一级缓存
getChangedScrapViewForPosition( ):根据position从mChangedScrap中获取; - 二级缓存
getScrapOrHiddenOrCachedHolderForPosition( )、getScrapOrCachedViewForId():优先根据position从mAttachScrap、mCacheViews中获取(按位置查找未变更的缓存),其次根据Id再次从mAttachScrap、mCacheViews中获取; - 三级缓存
mViewCacheExtension.getViewForPositionAndType( ),自定义缓存,一定注意这里返回的是 View; - 四级缓存
getRecycledViewPool().getRecycledView( ),根据itemViewType获取对应 ViewHolder 的缓存 ArrayList,再从其中尝试获取。
如果上述的所有步骤都没有复用到 ViewHolder,则调用 mAdapter.createViewHolder( ) 方法直接新建一个 ViewHolder,此时调用到的就是 adapter 中的 onCreateViewHolder( ) 方法。
上述所有过程运行结束之后,此时不论是通过复用还是新建,holder 都已经不为 null 了,则会调用 tryBindViewHolderByDeadline( ) 方法进行数据处理,adapter 的 onBindViewHolder( ) 方法就是在这里调用到的。