RecyclerView源码解析

RecyclerView源码解析

2023年7月15日发(作者:)

RecyclerView源码解析复⽤和回收复⽤的好处:避免为表项视图绑定数据,创建表项视图。⼦item的绘制交给LayoutManager去处理。fillLinearLayoutManager#fill作⽤:回收和复⽤。int fill(er recycler, LayoutState layoutState, state, boolean stopOnFocusable) { ... // 当前的⽅向上是否还有多余的空间填充item int remainingSpace = able + ; LayoutChunkResult layoutChunkResult = mLayoutChunkResult; // 当剩余空间> 0时,继续填充更多表项 while ((ite || remainingSpace > 0) && e(state)) { // 通过View循环,来对条⽬进⾏⼀条条复⽤,填充剩余空间 layoutChunk(recycler, state, layoutState, layoutChunkResult);

if (lingOffset != ING_OFFSET_NaN) { // 从剩余空间中扣除新表项占⽤像素值 lingOffset += med; if (able < 0) { // 在limit上追加新表项所占像素值 // 回收哪些项⽬是根据limit线⾛的,⼿指向上滑,底部填充元素,limit线会下移,在这根线上⾯的条⽬会被回收。 lingOffset += able; } // 回收 recycleByLayoutState(recycler, layoutState); } } return start - able;}回收LinearLarLayoutManager#recycleByLayoutStateprivate void recycleByLayoutState(er recycler, LayoutState layoutState) { if (tDirection == _START) { // 从列表头回收 recycleViewsFromEnd(recycler, lingOffset); } else { // 从列表尾回收 recycleViewsFromStart(recycler, lingOffset); }}LinearLarLayoutManager#recycleViewsFrsFroomStartprivate void recycleViewsFromStart(er recycler, int dt) { //从头开始遍历 LinearLayoutManager,以找出应该会回收的表项 final int childCount = getChildCount(); // 是否反转布局,就是布局上从上往下填充还是从下往上填充 if (mShouldReverseLayout) { for (int i = childCount - 1; i >= 0; i--) { View child = getChildAt(i); // 当某表项底部位于limit隐形线之后时,回收它以上的所有表项 // limit是列表中隐形的线 if (oratedEnd(child) > limit || nsformedEndWithDecoration(child) > limit) { //回收索引为末尾到i-1的表项 recycleChildren(recycler, childCount - 1, i); return; } } } else { //回收索引为0到i-1的表项 for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (oratedEnd(child) > limit || nsformedEndWithDecoration(child) > limit) { recycleChildren(recycler, 0, i); return; } } }}“从列表头回收表项”所对应的场景是:⼿指上滑,列表向上滚动,新的表项逐个插⼊到列表尾部,列表头部的表项逐个被回收。复⽤void layoutChunk(er recycler, state, LayoutState layoutState, LayoutChunkResult result) { // 1. 通过缓存池中获取下个条⽬ View view = (recycler);

// 2. 将列表中的⼀项添加进RecyclerView Params params = (Params) outParams(); // 3. 测量该视图 measureChildWithMargins(view, 0, 0); // 4. 获取填充视图需要消耗的像素值 med = oratedMeasurement(view); // 5. 布局表项 // 确定表项上下左右四个点相对于RecyclerView的位置 layoutDecoratedWithMargins(view, left, top, right, bottom);}LinearLarLayoutManager#nextView next(er recycler) { if (mScrapList != null) { return nextViewFromScrapList(); } final View view = wForPosition(mCurrentPosition); mCurrentPosition += mItemDirection; return view;}RecyclerView#tryGetViewHolderFolderForPositionByDeadline复⽤机制代码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 // 复⽤的对象是ViewHolder // 在布局之前 if (ayout()) { // 1. 通过id或者position从mChangedScrap缓存找到对应的缓存 holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder != null; } if (holder == null) { // 2. 通过position从mAttachedScrap或⼆级回收缓存中获取ViewHolder holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); if (holder != null) { if (!validateViewHolderForOffsetPosition(holder)) { if (!dryRun) { gs(_INVALID); if (p()) { removeDetachedView(ew, false); p(); } else if (urnedFromScrap()) { eturnedFromScrapFlag(); } recycleViewHolderInternal(holder); } holder = null; } else { fromScrapOrHiddenOrCache = true; } } } if (holder == null) { final int offsetPosition = sitionOffset(position); final int type = mViewType(offsetPosition); // 3. 通过id从mAttachedScrap或⼆级回收缓存中获取ViewHolder if (bleIds()) { holder = getScrapOrCachedViewForId(mId(offsetPosition), type, dryRun); if (holder != null) { // update position ion = offsetPosition; fromScrapOrHiddenOrCache = true; } } if (holder == null && mViewCacheExtension != null) { // 4. 从⾃定义缓存中获取ViewHolder final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); if (view != null) { holder = getChildViewHolder(view); } } if (holder == null) { // fallback to pool // 5. 从缓存池中取ViewHolder holder = getRecycledViewPool().getRecycledView(type); if (holder != null) { nternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); } } } if (holder == null) { long start = getNanoTime(); // 6.所有缓存都没命中,就需要创建ViewHolder holder = ViewHolder(, type); holder = ViewHolder(, type); if (ALLOW_THREAD_GAP_WORK) { // only bother finding nested RV if prefetching RecyclerView innerView = findNestedRecyclerView(ew); if (innerView != null) { dRecyclerView = new WeakReference<>(innerView); } } long end = getNanoTime(); InCreateTime(type, end - start); if (DEBUG) { Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); } } } if (fromScrapOrHiddenOrCache && !ayout() && holder .hasAnyOfTheFlags(_BOUNCED_FROM_HIDDEN_LIST)) { gs(0, _BOUNCED_FROM_HIDDEN_LIST); if (mpleAnimations) { int changeFlags = ItemAnimator .buildAdapterChangeFlagsForAnimations(holder); changeFlags |= _APPEARED_IN_PRE_LAYOUT; final ItemHolderInfo info = PreLayoutInformation(mState, holder, changeFlags, odifiedPayloads()); recordAnimationInfoIfBouncedHiddenView(holder, info); } } boolean bound = false; if (ayout() && d()) { youtPosition = position; } else if (!d() || pdate() || lid()) { final int offsetPosition = sitionOffset(position); //获得ViewHolder后,绑定视图数据 bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); } ... return holder;}总结:RecyclerView在滚动发⽣之前,会根据预计滚动位移⼤⼩来决定需要向列表中填充多少新的表项。在填充表项的同时,也会回收表项,回收的依据是limit隐形线。limit隐形线是RecyclerView在滚动发⽣之前根据滚动位移计算出来的⼀条线,它是决定哪些表项该被回收的重要依据。它可以理解为:隐形线当前所在位置,在滚动完成后会和列表顶部重叠。limit隐形线的初始值=列表当前可见表项的底部到列表底部的距离,即列表在不填充新表项时,可以滑动的最⼤距离。每⼀个新填充表项消耗的像素值都会被追加到limit值之上,即limit隐形线会随着新表项的填充⽽不断地下移。触发回收逻辑时,会遍历当前所有表项,若某表项的底部位于limit隐形线下⽅,则该表项上⽅的所有表项都会被回收。四级缓存// detach调⽤// 复⽤的时候不需要调⽤bindViewHolder重新绑定数据,状态和数据不会被重置的// 保存原封不动的ViewHolder// ⽣命周期两次布局// 位置⼀致才能复⽤final ArrayList mAttachedScrap = new ArrayList<>();// 发⽣变化的ViewHolder// ⽣命周期只有预布局ArrayList mChangedScrap = null;// remove调⽤// 可通过setItemCacheSize调整,默认⼤⼩为2// 上下滑动,被滑出去的ViewHolder缓存// 如果超过限制,会把最⽼的item移除到RecycledViewPool中。// mCachedViews中缓存的ViewHolder只能复⽤于指定位置,不需要调⽤bindViewHolder重新绑定数据// 应⽤场景列表回滚final ArrayList mCachedViews = new ArrayList();// ⾃定义拓展View缓存private ViewCacheExtension mViewCacheExtension;// RecycledViewPool中的ViewHolder存储在SparseArray中,并且按viewType分类存储// 同⼀类型的ViewHolder存放在ArrayList 中,且默认最多存储5个。// mCachedViews缓存放不下的时候,才会把缓存放进mRecyclerPool,⾥⾯的缓存都是需要重新绑定数据的。// 从mRecyclerPool中取出的ViewHolder只能复⽤于相同viewType的表项。RecycledViewPool mRecyclerPool;最差情况:重新创建ViewHolder绑定数据次好情况:复⽤ViewHolder需要重新绑定数据最好情况:复⽤ViewHolder不需要重新绑定数据谈谈mChangedScrap⽣命周期只有预布局的时候。mChangedScrap的调⽤场景是notifyItemChanged和notifyItemRangeChanged,只有发⽣变化的ViewHolder才会放⼊到mChangedScrap中。mChangedScrap缓存中的ViewHolder是需要调⽤onBindViewHolder⽅法重新绑定数据的。浅谈⼏种更新RecyclerView的区别notifyItemInserted需要重新布局,A、B、C都可以从mAttachedScrap缓存拿出来直接使⽤,不需要绑定,a需要创建对应的ViewHolder重新绑定,添加进⼀级缓存。注意如果是A,B,C,D移除B,B还是在mAttachedScrap缓存,只不过FLAG是REMOVE。notifyDataSetChanged代表数据全⾯发⽣变化,屏幕上的内容标为⽆效,屏幕上的元素全部缓存到四级缓存RecycledViewPool,屏幕上的元素都需要重新绑定。notifyItemChangedA被添加了FLAG_UPDATE,在scrapView(View view)中不满⾜!ted()所以会被放⼊到mChangedScrap,然后在缓存复⽤时B、C、D都可以直接使⽤,A因为被修改了所以需要重新绑定⼀下。也就是说:notifyItemChanged将屏幕上的元素保存到⼀级缓存中,有更改的保存到mChangedScrap中并且需要重新绑定,没有变化的保存到mAttachedScrap中。重点类Recycler:管理复⽤LayoutManager:管理布局detach和remove⼀个View只是暂时被清除掉,稍后⽴刻就要⽤到,使⽤detach。它会被缓存进scrapCache的区域。⼀个View不再显⽰在屏幕上,需要被清除掉,并且下次再显⽰它的时机⽬前未知,使⽤remove。它会被以viewType分组,缓存进RecyclerViewPool⾥。scrap view的⽣命周期在将表项⼀个个填充到列表之前会先将其先回收到mAttachedScrap中,回收数据的来源是LayoutManager的孩⼦,⽽LayoutManager的孩⼦都是屏幕上可见的或即将可见的表项。RecyclerView布局的最后⼀步,清除scrap view。mAttachedScrap⽣命周期起始于RecyclerView布局开始,终⽌于RecyclerView布局结束。RecyclerView的动画列表中有两个表项(1、2),删除2,此时3会从屏幕底部平滑地移⼊并占据原来2的位置。为了实现该效果,RecyclerView的策略是:为动画前的表项先执⾏⼀次pre-layout,将不可见的表项3也加载到布局中,形成⼀张布局快照(1、2、3)。再为动画后的表项执⾏⼀次post-layout,同样形成⼀张布局快照(1、3)。⽐对两张快照中表项3的位置,就知道表项3该如何做动画了,表项2做消失动画,当动画结束后,item2的ViewHolder会被回收。RecyclerView为了实现表项动画,进⾏了2次布局(预布局+后布局),在源码上表现为utChildren()被调⽤2次。预布局的过程始于chLayoutStep1(),终于chLayoutStep2()。在每次向RecyclerView填充表项之前都会先清空LayoutManager中现存表项,将它们detach并同时缓存⼊mAttachedScrap列表中。在紧接着的填充表项阶段,就⽴马从mAttachedScrap中取出刚被 detach的表项并重新attach它们。pre-layoutLinearLayoutManager#onLayoutChildrenpublic void onLayoutChildren(er recycler, state) { ... // 在填充表项之前会遍历所有⼦表项,并逐个回收 detachAndScrapAttachedViews(recycler); ... // 填充表项 fill()}post-layout因为LayoutManager中现有表项1、2、3,所以scrap完成后,mAttachedScrap中存有表项1、2、3的ViewHolder实例(position依次为0、0、1,被移除表项的position会被置0)。分别填充position位置为0和1的表项。为2的位置缓存就不会命中。缓存命中规则:position相同,并且表项没被移除。为什么这么设计:为了确定动画的种类和起终点,需要⽐对动画前和动画后的两张“表项快照”,不然只知道最终位置不知道起始位置。为了获得两张快照,就得布局两次,分别是预布局和后布局(布局即是往列表中填充表项),为了让两次布局互不影响,就不得不在每次布局前先清除上⼀次布局的内容(就好⽐先清除画布,重新作画),但是两次布局中所需的某些表项⼤概率是⼀摸⼀样的,若在清除画布时,把表项的所有信息都⼀并清除,那重新作画时就会花费更多时间(重新创建 ViewHolder 并绑定数据),RecyclerView 采取了⽤空间换时间的做法:在清除画布时把表项缓存在scrap结构中,以便在填充表项可以命中缓存,以缩短填充表项耗时。整体总结Recycler有4个层次⽤于缓存ViewHolder对象,优先级从⾼到底依次为ArrayList mAttachedScrap、ArrayList

mCachedViews、ViewCacheExtension mViewCacheExtension、RecycledViewPool mRecyclerPool。如果四层缓存都未命中,则重新创建并绑定ViewHolder对象。缓存性能:都不需要重新创建ViewHolder,只有RecycledViewPool,mChangedScrap需要重新绑定数据。缓存容量:mAttachedScrap:没有⼤⼩限制,但最多包含屏幕可见表项。mCachedViews:默认⼤⼩限制为2,放不下时,按照先进先出原则将最先进⼊的ViewHolder存⼊回收池以腾出空间。mRecyclerPool:对ViewHolder按viewType分类存储(通过SparseArray),同类ViewHolder存储在默认⼤⼩为5的ArrayList中。缓存⽤途:mAttachedScrap:⽤于布局过程中屏幕可见表项的回收和复⽤。mCachedViews:⽤于移出屏幕表项的回收和复⽤,且只能⽤于指定位置的表项,有点像“回收池预备队列”,即总是先回收到mCachedViews,当它放不下的时候,按照先进先出原则将最先进⼊的ViewHolder存⼊回收池。mRecyclerPool:⽤于移出屏幕表项的回收和复⽤,且只能⽤于指定viewType的表项缓存结构:mAttachedScrap:ArrayListmCachedViews:ArrayListmRecyclerPool:对ViewHolder按viewType分类存储在SparseArray中,同类ViewHolder存储在ScrapData中的ArrayList中参考RecyclerView 源码分析2-缓存机制图解RecyclerView ⾯试题 | 哪些情况下表项会被回收到缓存池?

发布者:admin,转转请注明出处:http://www.yc00.com/web/1689431291a247187.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信