}
而`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鸿蒙开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上HarmonyOS鸿蒙开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新
如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年HarmonyOS鸿蒙开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-CwhfoYqb-1712859615795)]
[外链图片转存中…(img-IeVKNcz2-1712859615796)]
[外链图片转存中…(img-UU5Cfnn0-1712859615796)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上HarmonyOS鸿蒙开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新
如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
[外链图片转存中…(img-rHZxUcEw-1712859615796)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!