RecyclerView的回收缓存均由内部类Recycler完成

}


而`getViewForPosition` 又会调用到`tryGetViewHolderForPositionByDeadline`方法:



//com.android.internal.widget.RecyclerView.Recycler#tryGetViewHolderForPositionByDeadline
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
if (position < 0 || position >= mState.getItemCount()) {
throw new IndexOutOfBoundsException("Invalid item position " + position
+ “(” + position + “). Item count:” + mState.getItemCount());
}
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
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
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + “(offset:” + offsetPosition + “).”
+ “state:” + mState.getItemCount());
}

    final int type = mAdapter.getItemViewType(offsetPosition);
    // 2) Find from scrap/cache via stable ids, if exists
    if (mAdapter.hasStableIds()) {
        holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                type, dryRun);
        if (holder != null) {
            // update position
            holder.mPosition = offsetPosition;
            fromScrapOrHiddenOrCache = true;
        }
    }
    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) {
                throw new IllegalArgumentException("getViewForPositionAndType returned"
                        + " a view which does not have a ViewHolder");
            } else if (holder.shouldIgnore()) {
                throw new IllegalArgumentException("getViewForPositionAndType returned"
                        + " a view that is ignored. You must call stopIgnoring before"
                        + " returning this view.");
            }
        }
    }
    if (holder == null) { // fallback to pool
        if (DEBUG) {
            Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
                    + position + ") fetching from shared pool");
        }
        holder = getRecycledViewPool().getRecycledView(type);
        if (holder != null) {
            holder.resetInternal();
            if (FORCE_INVALIDATE_DISPLAY_LIST) {
                invalidateDisplayListInt(holder);
            }
        }
    }
    if (holder == null) {
        long start = getNanoTime();
        if (deadlineNs != FOREVER_NS
                && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
            // abort - we have a deadline we can't meet
            return null;
        }
        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);
            }
        }

        long end = getNanoTime();
        mRecyclerPool.factorInCreateTime(type, end - start);
        if (DEBUG) {
            Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
        }
    }
}

// This is very ugly but the only place we can grab this information
// before the View is rebound and returned to the LayoutManager for post layout ops.
// We don't need this in pre-layout since the VH is not updated by the LM.
if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
        .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
    holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
    if (mState.mRunSimpleAnimations) {
        int changeFlags = ItemAnimator
                .buildAdapterChangeFlagsForAnimations(holder);
        changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
        final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                holder, changeFlags, holder.getUnmodifiedPayloads());
        recordAnimationInfoIfBouncedHiddenView(holder, info);
    }
}

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()) {
    if (DEBUG && holder.isRemoved()) {
        throw new IllegalStateException("Removed holder should be bound and it should"
                + " come here only in pre-layout. Holder: " + holder);
    }
    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;

}


### 2.2 根据位置从mAttachedScrap和mCachedViews中获取View Holder


* #### 2.2.1 getScrapOrHiddenOrCachedHolderForPosition



ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();

// Try first for an exact, non-invalid match from scrap.
for (int i = 0; i < scrapCount; i++) {
    final ViewHolder holder = mAttachedScrap.get(i);
    if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
            && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
        holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
        return holder;
    }
}

if (!dryRun) {
    View view = mChildHelper.findHiddenNonRemovedView(position);
    if (view != null) {
       ...
       // 可忽略
        return vh;
    }
}

// Search in our first-level recycled view cache.
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
    final ViewHolder holder = mCachedViews.get(i);
    // invalid view holders may be in cache if adapter has stable ids as they can be
    // retrieved via getScrapOrCachedViewForId
    if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
        if (!dryRun) {
            mCachedViews.remove(i);
        }
        if (DEBUG) {
            Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
                    + ") found match in cache: " + holder);
        }
        return holder;
    }
}
return null;

}


* 第一步先从`mAttachedScrap`中获取`viewHolder`:如果可以拿到指定的`viewHolder`,接着判断:


	+ 如果`viewHolder`不是从`Scrap`中返回的(标志位不包含`FLAG_RETURNED_FROM_SCRAP`);
	+ `viewHolder.mPosition == position`
	+ `viewHolder`为有效
	+ 为预布局或者`viewHolder`的状态不是`removed`
* 如果第一步没有获得有效的`viewHolder`,第二步从`mChildHelper`中获取Hidden状态的View,但通常RecyclerView中没有Hidden状态的View,因为在addView的时候,就将hide值置为false
* 如果前两步都没有获得有效的viewHolder,则会从`mCachedView`中取得viewHolder,由于dryRun参数值默认为false,所以取出来的viewHolder会被remove掉


#### 2.1.2 检查viewHolder是否有效


#validateViewHolderForOffsetPosition:



boolean validateViewHolderForOffsetPosition(ViewHolder holder) {
// if it is a removed holder, nothing to verify since we cannot ask adapter anymore
// if it is not removed, verify the type and id.
if (holder.isRemoved()) {
if (DEBUG && !mState.isPreLayout()) {
throw new IllegalStateException(“should not receive a removed view unless it”
+ " is pre layout");
}
return mState.isPreLayout();
}
if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder "
+ “adapter position” + holder);
}
if (!mState.isPreLayout()) {
// don’t check type if it is pre-layout.
final int type = mAdapter.getItemViewType(holder.mPosition);
if (type != holder.getItemViewType()) {
return false;
}
}
if (mAdapter.hasStableIds()) {
return holder.getItemId() == mAdapter.getItemId(holder.mPosition);
}
return true;
}


如果步骤1 返回null,那么会进行步骤2.3:


### 2.3 根据Id从mAttachedScrap和mCachedViews中获取viewHolder



// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}


* 可以看到,当Adapter有固定ID时,我们可以根据Id来获取viewHolder,但是默认情况下,需要我们进行以下两个步骤才会生效:
* + 设置`Apapter.setHasStableIds(true)`
	+ 重写`Adapter.getItemId(position)`方法



//com.android.internal.widget.RecyclerView.Recycler#getScrapOrCachedViewForId
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
// Look in our attached views first
final int count = mAttachedScrap.size();
for (int i = count - 1; i >= 0; i–) {
final ViewHolder holder = mAttachedScrap.get(i);
if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
if (type == holder.getItemViewType()) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
if (holder.isRemoved()) {
// this might be valid in two cases:
// > item is removed but we are in pre-layout pass
// >> do nothing. return as is. make sure we don’t rebind
// > item is removed then added to another position and we are in
// post layout.
// >> remove removed and invalid flags, add update flag to rebind
// because item was invisible to us and we don’t know what happened in
// between.
if (!mState.isPreLayout()) {
holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
}
}
return holder;
} else if (!dryRun) {
// if we are running animations, it is actually better to keep it in scrap
// but this would force layout manager to lay it out which would be bad.
// Recycle this scrap. Type mismatch.
mAttachedScrap.remove(i);
removeDetachedView(holder.itemView, false);
quickRecycleScrapView(holder.itemView);
}
}
}

// Search the first-level cache
final int cacheSize = mCachedViews.size();
for (int i = cacheSize - 1; i >= 0; i--) {
    final ViewHolder holder = mCachedViews.get(i);
    if (holder.getItemId() == id) {
        if (type == holder.getItemViewType()) {
            if (!dryRun) {
                mCachedViews.remove(i);
            }
            return holder;
        } else if (!dryRun) {
            recycleCachedViewAt(i);
            return null;
        }
    }
}
return null;

}


根据`Id`从`mAttachedScrap`和`mCachedViews`中获取`viewHolder`,如果获取到了并且`ItemViewType`一致,则直接返回


### 2.4 从mRecyclerPool中获取viewHolder



//3.1 获取viewHolder
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
// 3.2 重置viewHolder
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}


#### 2.4.1 获取viewHolder:getRecycledView



//com.android.internal.widget.RecyclerView.RecycledViewPool#getRecycledView
public ViewHolder getRecycledView(int viewType) {
// 根据viewType获取对应的viewHolder缓存
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList scrapHeap = scrapData.mScrapHeap;
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}


#### 2.4.2 重置viewHolder状态



void resetInternal() {
mFlags = 0;
mPosition = NO_POSITION;
mOldPosition = NO_POSITION;
mItemId = NO_ID;
mPreLayoutPosition = NO_POSITION;
mIsRecyclableCount = 0;
mShadowedHolder = null;
mShadowingHolder = null;
clearPayload();
mWasImportantForAccessibilityBeforeHidden = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
clearNestedRecyclerViewIfNotNested(this);
}


### 2.5 创建新的viewHolder


如果前三步得到的holder为空,则需要新建一个viewHolder:



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


### 2.6 根据viewHolder状态确定是否需要调用bindViewHolder



if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException(“Removed holder should be bound and it should”
+ " come here only in pre-layout. Holder: " + holder);
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
// 重写bindView
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}




---



private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
int position, long deadlineNs) {
holder.mOwnerRecyclerView = RecyclerView.this;
final int viewType = holder.getItemViewType();
long startBindNs = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
// abort - we have a deadline we can’t meet
return false;
}
// 熟悉的bindViewHolder
mAdapter.bindViewHolder(holder, offsetPosition);
long endBindNs = getNanoTime();
mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
attachAccessibilityDelegate(holder.itemView);
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
return true;
}


3. ## 回收机制


### 3.1 回收方法


LayoutManager提供了各种回收方法:



detachAndScrapView(View child, Recycler recycler)
detachAndScrapViewAt(int index, Recycler recycler)
detachAndScrapAttachedViews(Recycler recycler)

removeAndRecycleView(View child, Recycler recycler)
removeAndRecycleViewAt(int index, Recycler recycler)
removeAndRecycleAllViews(Recycler recycler)


* 前三个方法负责将View回收到一级缓存(`Recycler.mAttachedMap`)中,而一级缓存只是一个临时缓存,用于初始化或者数据集变化时,将所有的View放到临时放到缓存中,即**只在布局(调用\*\*\*\*`onLayoutChildren`** **)时才会调用(** **`detachAndScrapAttachedViews`** **)** ,`detachAndScrapView/detachAndScrapViewAt`没有看到有调用的地方)。
* 后三个方法负责将View回收到二级缓存(`mCachedViews`)或者四级缓存(`RecyclerViewPool`)中,`mCachedViews`默认大小为2(但目前存在`mPrefetchMaxCountObserved`参数,值为1 ,所以`mCachedViews`大小可能为3)


### 3.2 回收时机


#### 3.2.1 回收到一级缓存的时机


只有在Adapter数据集发生变化,调用各种notify方法,因此重新布局后才会对屏幕内的View回收到一级缓存(`mAttachedMap`)中,此时由`onLayoutChildren`发起,调用**detachAndScrapAttachedViews**方法,进而调用到`scrapOrRecycleView`方法:



private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);

if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
// 回收到一级缓存
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}


当viewHolder满足 无效/没有被移除/Adapter没有`stableIds` 时,会回收到二级缓存或四级缓存中,否则回收到一级缓存


#### 3.2.2 回收到二级缓存或者四级缓存的时机


1. **数据集发生变化**,调用`LayoutManage.onLayoutChildren` -> `LayoutManage.detachAndScrapAttachedViews` -> `recycler.recycleViewHolderInternal` 进行回收;
2. **item滑出屏幕时**,调用 `scrollBy` -> `fill`-> `recycleByLayoutState` 进行回收


由于上述两种情况下最终调用的地方有重合,我们直接以第二种情况来做说明:


**整体时序图如下**:


![](https://img-blog.csdnimg.cn/img_convert/141f96925505621b7711081688aee447.png)


用户在滑动列表时,会使用`LinearLayoutManage.scrollBy`函数:



int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {

updateLayoutState(layoutDirection, absDelta, true, state);
// 关键点:调用fill函数
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);

mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
}


继续深入`fill`函数:



//androidx.recyclerview.widget.LinearLayoutManager#fill

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {

if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
    // TODO ugly bug fix. should not happen
    if (layoutState.mAvailable < 0) {
        layoutState.mScrollingOffset += layoutState.mAvailable;
    }
    // 1. 回收到二级缓存或四级缓存的起点
    recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
    layoutChunkResult.resetInternal();
    ...
    layoutChunk(recycler, state, layoutState, layoutChunkResult);
    ...
    layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
    
    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
        layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
        if (layoutState.mAvailable < 0) {
            layoutState.mScrollingOffset += layoutState.mAvailable;
        }
        recycleByLayoutState(recycler, layoutState);
    }
   
}
return start - layoutState.mAvailable;

}


#`recycleByLayoutState`



private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (!layoutState.mRecycle || layoutState.mInfinite) {
return;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数HarmonyOS鸿蒙开发工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年HarmonyOS鸿蒙开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上HarmonyOS鸿蒙开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
img

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年HarmonyOS鸿蒙开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-CwhfoYqb-1712859615795)]
[外链图片转存中…(img-IeVKNcz2-1712859615796)]
[外链图片转存中…(img-UU5Cfnn0-1712859615796)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上HarmonyOS鸿蒙开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
[外链图片转存中…(img-rHZxUcEw-1712859615796)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值