ViewPager2系列:
- 图解RecyclerView缓存复用机制
- 图解RecyclerView预拉取机制
- 图解ViewPager2离屏加载机制(上)
回忆上一篇文章,咱们为了削减描述问题的维度,于演示之前附加了许多约束条件,比如禁用了RecyclerView的预拉取机制。
实践上,预拉取(prefetch)机制作为RecyclerView的重要特性之一,常常与缓存复用机制一同配合运用、一起协作,极大地提高了RecyclerView全体滑动的流通度。
而且,这种特性在ViewPager2中相同得以保存,对ViewPager2滑动效果的呈现也起着要害性的作用。因此,咱们ViewPager2系列的第二篇,便是要来侧重介绍RecyclerView的预拉取机制。
预拉取是指什么?
在核算机术语中,预拉取指的是在已知需求某部分数据的前提下,使用体系资源搁置的空档,预先拉取这部分数据到本地,然后进步履行时的功率。
具体到RecyclerView预拉取的情境则是:
- 使用UI线程正好处于闲暇状况的机遇
- 预先拉取待进入屏幕区域内的一部分列表项视图并缓存起来
- 然后削减因视图创立或数据绑定等耗时操作所引起的卡顿。
预拉取是怎样完结的?
正如把缓存复用的实践作业托付给了其内部的Recycler
类相同,RecyclerView也把预拉取的实践作业托付给了一个名为GapWorker
的类,其内部的作业流程,能够用以下这张思想导图来归纳:
接下来咱们就循着这张思想导图,来一一拆解预拉取的作业流程。
1.建议预拉取作业
经过查找对GapWorker目标的引用,咱们能够整理出3个建议预拉取作业的机遇,别离是:
- RecyclerView被拖动(Drag)时
@Override
public boolean onTouchEvent(MotionEvent e) {
...
switch (action) {
...
case MotionEvent.ACTION_MOVE: {
...
if (mScrollState == SCROLL_STATE_DRAGGING) {
...
// 处于拖动状况而且存在有用的拖动间隔时
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
}
break;
...
}
...
return true;
}
- RecyclerView惯性滑动(Fling)时
class ViewFlinger implements Runnable {
...
@Override
public void run() {
...
if (!smoothScrollerPending && doneScrolling) {
...
} else {
...
if (mGapWorker != null) {
mGapWorker.postFromTraversal(RecyclerView.this, consumedX, consumedY);
}
}
}
...
}
- RecyclerView嵌套翻滚时
private void nestedScrollByInternal(int x, int y, @Nullable MotionEvent motionEvent, int type) {
...
if (mGapWorker != null && (x != 0 || y != 0)) {
mGapWorker.postFromTraversal(this, x, y);
}
...
}
2.履行预拉取作业
GapWorker
是Runnable接口的一个完结类,意味着其履行作业的进口必定是在run办法。
final class GapWorker implements Runnable {
@Override
public void run() {
...
prefetch(nextFrameNs);
...
}
}
在run办法内部咱们能够看到其调用了一个prefetch
办法,在进入该办法之前,咱们先来剖析传入该办法的参数。
// 查询最近一个笔直同步信号宣布的时刻,以便咱们能够猜测下一个
final int size = mRecyclerViews.size();
long latestFrameVsyncMs = 0;
for (int i = 0; i < size; i++) {
RecyclerView view = mRecyclerViews.get(i);
if (view.getWindowVisibility() == View.VISIBLE) {
latestFrameVsyncMs = Math.max(view.getDrawingTime(), latestFrameVsyncMs);
}
}
...
// 猜测下一个笔直同步信号宣布的时刻
long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs;
prefetch(nextFrameNs);
由该办法的实参命名nextFrameNs
可知,传入的是下一帧开端制作的时刻。
了解过Android屏幕刷新机制的人都知道,当GPU烘托完图形数据并放入图画缓冲区(buffer)之后,显现屏(Display)会等候笔直同步信号(Vsync)宣布,随即交流缓冲区并取出缓冲数据,然后开端对新的一帧的制作。
所以,这个实参一起也表明下一个笔直同步信号(Vsync)宣布的时刻,这是个猜测值,单位为纳秒。由最近一个笔直同步信号宣布的时刻(latestFrameVsyncMs
),加上每一帧刷新的间隔时刻(mFrameIntervalNs
)核算而成。
其间,每一帧刷新的间隔时刻是这姿态核算得到的:
// 假如取自显现屏的刷新率数据有用,则不选用默许的60fps
// 注意:此查询咱们只静态地履行一次,由于它十分贵重(>1ms)
Display display = ViewCompat.getDisplay(this);
float refreshRate = 60.0f; // 默许的刷新率为60fps
if (!isInEditMode() && display != null) {
float displayRefreshRate = display.getRefreshRate();
if (displayRefreshRate >= 30.0f) {
refreshRate = displayRefreshRate;
}
}
mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate); // 1000000000纳秒=1秒
也即假定在默许60fps的刷新率下,每一帧刷新的间隔时刻应为16.67ms。
再由该办法的形参命名deadlineNs
可知,传入的参数表明的是预抓取作业完结的最终期限:
void prefetch(long deadlineNs) {
...
}
归纳一下便是,预抓取的作业有必要鄙人一个笔直同步信号宣布之前,也即下一帧开端制作之前完结。
什么意思呢?
这是由于从Android 5.0(API等级21)开端,出于进步UI烘托功率的考虑,Android体系引入了RenderThread机制,即烘托线程。这个机制担任接收原先主线程中繁重的UI烘托作业,使得主线程能够更加专心于与用户的交互,然后大幅进步页面的流通度。
但这里有一个问题。
当UI线程提前完结作业,并将一个帧传递给RenderThread烘托之后,就会进入所谓的休眠状况,呈现了很多的闲暇时刻,直至下一帧开端制作之前。如图所示:
一方面,这些UI线程上的闲暇时刻并没有被使用起来,相当于宝贵的线程资源被白白浪费掉;
另一方面,新的列表项进入屏幕时,又需求在UI线程的输入阶段(Input)就完结视图创立与数据绑定的作业,这会推延UI线程及RenderThread上的其他作业,假如这些被推延的作业无法鄙人一帧开端制作之前完结,就有可能造成界面上的丢帧卡顿。
GapWorker正是选择在此时刻窗口内安排预拉取的作业,也即把创立和绑定的耗时操作,移到UI线程的闲暇时刻内完结,与原先的RenderThread并行履行。
但这个预拉取的作业相同有必要鄙人一帧开端制作之前完结,否则预拉取的列表项视图仍是会无法被及时地制作出来,进而导致丢帧卡顿,所以才有了前面表明最终期限的传入参数。
了解完这个参数的意义后,让咱们继续往下阅览源码。
2.1 构建预拉取使命列表
void prefetch(long deadlineNs) {
buildTaskList();
...
}
进入prefetch办法后能够看到,预拉取的第一个动作便是先构建预拉取的使命列表,其内部又可分为以下3个事项:
2.1.1 搜集预拉取的列表项数据
private void buildTaskList() {
// 1.搜集预拉取的列表项数据
final int viewCount = mRecyclerViews.size();
int totalTaskCount = 0;
for (int i = 0; i < viewCount; i++) {
RecyclerView view = mRecyclerViews.get(i);
// 仅对当时可见的RecyclerView搜集数据
if (view.getWindowVisibility() == View.VISIBLE) {
view.mPrefetchRegistry.collectPrefetchPositionsFromView(view, false);
totalTaskCount += view.mPrefetchRegistry.mCount;
}
}
...
}
static class LayoutPrefetchRegistryImpl
implements RecyclerView.LayoutManager.LayoutPrefetchRegistry {
...
void collectPrefetchPositionsFromView(RecyclerView view, boolean nested) {
...
// 启用了预拉取机制
if (view.mAdapter != null
&& layout != null
&& layout.isItemPrefetchEnabled()) {
if (nested) {
...
} else {
// 根据移动量进行预拉取
if (!view.hasPendingAdapterUpdates()) {
layout.collectAdjacentPrefetchPositions(mPrefetchDx, mPrefetchDy,
view.mState, this);
}
}
...
}
}
}
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
LayoutPrefetchRegistry layoutPrefetchRegistry) {
// 根据布局方向取水平方向的移动量dx或笔直方向的移动量dy
int delta = (mOrientation == HORIZONTAL) ? dx : dy;
...
ensureLayoutState();
// 根据移动量正负值判别移动方向
final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDelta = Math.abs(delta);
// 搜集与预拉取相关的重要数据,并存储到LayoutState
updateLayoutState(layoutDirection, absDelta, true, state);
collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry);
}
}
这一事项主要是根据RecyclerView翻滚的方向,搜集行将进入屏幕的、待预拉取的列表项数据,其间,最要害的2项数据是:
- 待预拉取项的position值——用于预加载项方位的承认
- 待预拉取项与RecyclerView可见区域的间隔——用于预拉取使命的优先级排序
咱们以最简略的LinearLayoutManager
为例,看一下这2项数据是怎样搜集的,其最要害的完结就在于前面的updateLayoutState
办法。
假定此时咱们的手势是向上滑动的,则其进入的是layoutToEnd == true的判别:
private void updateLayoutState(int layoutDirection, int requiredSpace,
boolean canUseExistingSpace, RecyclerView.State state) {
...
if (layoutToEnd) {
...
// 过程1,获取翻滚方向上的第一个项
final View child = getChildClosestToEnd();
// 过程2,承认待预拉取项的方向
mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
: LayoutState.ITEM_DIRECTION_TAIL;
// 过程3,承认待预拉取项的position
mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
// 过程4,承认待预拉取项与RecyclerView可见区域的间隔
scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
- mOrientationHelper.getEndAfterPadding();
} else {
...
}
...
mLayoutState.mScrollingOffset = scrollingOffset;
}
过程1,获取RecyclerView翻滚方向上的第一项,如图中①所示:
过程2,承认待预拉取项的方向。不用回转布局的状况下是ITEM_DIRECTION_TAIL,该值等于1,如图中②所示:
过程3,承认待预拉取项的position值。由翻滚方向上的第一项的position值加上过程2承认的方向值相加得到,对应的是RecyclerView待进入屏幕区域的下一个项,如图中③所示:
过程4,承认待预拉取项与RecyclerView可见区域的间隔,该值由以下2个值相减得到:
-
getEndAfterPadding
:指的是RecyclerView去除了Padding后的底部方位,并不彻底等于RecyclerView的高度。 -
getDecoratedEnd
:指的是由列表项的底部方位,加上列表项建立的外边距,再加上列表项间隔的高度核算得到的值。
咱们用一张图来说明一下:
首要,图中的①表明一个完整的屏幕可见区域,其间:
- 深灰色区域对应的是RecyclerView建立的上下内边距,即Padding值。
- 中灰色区域对应的是RecyclerView的列表项分隔线,即Decoration。
- 浅灰色区域对应的是每一个列表项建立的外边距,即Margin值。
RecyclerView的实践可见区域,是由虚线a和虚线b所包围的区域,即去除了上下内边距之后的区域。getEndAfterPadding办法回来的值,即是虚线b地点的方位。
图中的②是对RecyclerView底部不可见区域的透视图,假定现在position=2的列表项的底部正好贴合到RecyclerView可见区域的底部,则getDecoratedEnd办法回来的值,即是虚线c地点的方位。
接下来,假如按前面的过程4进行核算,即用虚线c地点的方位减去的虚线b地点的方位,得到的便是图中的③,即刚好是列表项的外边距加上分隔线的高度。
这个成果便是待预拉取列表项与RecyclerView可见区域的间隔。跟着向上滑动的手势这个间隔值逐渐变小,直到正好进入RecyclerView的可见区域时变为0,随后开端预加载下一项。
这2项数据搜集到之后,就会调用GapWorker的addPosition
办法,以交错的形式寄存到一个int数组类型的mPrefetchArray
结构中去:
@Override
public void addPosition(int layoutPosition, int pixelDistance) {
...
// 根据实践需求分配新的数组,或以2的倍数扩展数组大小
final int storagePosition = mCount * 2;
if (mPrefetchArray == null) {
mPrefetchArray = new int[4];
Arrays.fill(mPrefetchArray, -1);
} else if (storagePosition >= mPrefetchArray.length) {
final int[] oldArray = mPrefetchArray;
mPrefetchArray = new int[storagePosition * 2];
System.arraycopy(oldArray, 0, mPrefetchArray, 0, oldArray.length);
}
// 交错寄存position值与间隔
mPrefetchArray[storagePosition] = layoutPosition;
mPrefetchArray[storagePosition + 1] = pixelDistance;
mCount++;
}
需求注意的是,RecyclerView每次的预拉取并不限于单个列表项,实践上,它能够一次获取多个列表项,比如运用了GridLayoutManager的状况。
2.1.2 根据预拉取的数据填充使命列表
private void buildTaskList() {
...
// 2.根据预拉取的数据填充使命列表
int totalTaskIndex = 0;
for (int i = 0; i < viewCount; i++) {
RecyclerView view = mRecyclerViews.get(i);
...
LayoutPrefetchRegistryImpl prefetchRegistry = view.mPrefetchRegistry;
final int viewVelocity = Math.abs(prefetchRegistry.mPrefetchDx)
+ Math.abs(prefetchRegistry.mPrefetchDy);
// 以2为偏移量进行遍历,从mPrefetchArray中别离取出前面存储的position值与间隔
for (int j = 0; j < prefetchRegistry.mCount * 2; j += 2) {
final Task task;
if (totalTaskIndex >= mTasks.size()) {
task = new Task();
mTasks.add(task);
} else {
task = mTasks.get(totalTaskIndex);
}
final int distanceToItem = prefetchRegistry.mPrefetchArray[j + 1];
// 与RecyclerView可见区域的间隔小于滑动的速度,该列表项必定可见,使命需求当即履行
task.immediate = distanceToItem <= viewVelocity;
task.viewVelocity = viewVelocity;
task.distanceToItem = distanceToItem;
task.view = view;
task.position = prefetchRegistry.mPrefetchArray[j];
totalTaskIndex++;
}
}
...
}
Task
是担任存储预拉取使命数据的实体类,其所包含特点的意义别离是:
-
position
:待预加载项的Position值 -
distanceToItem
:待预加载项与RecyclerView可见区域的间隔 -
viewVelocity
:RecyclerView的滑动速度,其实便是滑动间隔 -
immediate
:是否当即履行,判别根据是与RecyclerView可见区域的间隔小于滑动的速度 -
view
:RecyclerView自身
从第2个for循环能够看到,其是以2为偏移量进行遍历,从mPrefetchArray中别离取出前面存储的position值与间隔的。
2.1.3 对使命列表进行优先级排序
填充使命列表结束后,还要根据实践状况对使命进行优先级排序,其遵循的基本原则便是:越可能快进入RecyclerView可见区域的列表项,其预加载的优先级越高。
private void buildTaskList() {
...
// 3.对使命列表进行优先级排序
Collections.sort(mTasks, sTaskComparator);
}
static Comparator<Task> sTaskComparator = new Comparator<Task>() {
@Override
public int compare(Task lhs, Task rhs) {
// 首要,优先处理未清除的使命
if ((lhs.view == null) != (rhs.view == null)) {
return lhs.view == null ? 1 : -1;
}
// 然后考虑需求当即履行的使命
if (lhs.immediate != rhs.immediate) {
return lhs.immediate ? -1 : 1;
}
// 然后考虑滑动速度更快的
int deltaViewVelocity = rhs.viewVelocity - lhs.viewVelocity;
if (deltaViewVelocity != 0) return deltaViewVelocity;
// 最终考虑与RecyclerView可见区域间隔最短的
int deltaDistanceToItem = lhs.distanceToItem - rhs.distanceToItem;
if (deltaDistanceToItem != 0) return deltaDistanceToItem;
return 0;
}
};
2.2 调度预拉取使命
void prefetch(long deadlineNs) {
...
flushTasksWithDeadline(deadlineNs);
}
预拉取的第二个动作,则是将前面填充并排序好的使命列表顺次调度履行:
private void flushTasksWithDeadline(long deadlineNs) {
for (int i = 0; i < mTasks.size(); i++) {
final Task task = mTasks.get(i);
if (task.view == null) {
break; // 使命已完结
}
flushTaskWithDeadline(task, deadlineNs);
task.clear();
}
}
private void flushTaskWithDeadline(Task task, long deadlineNs) {
long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
task.position, taskDeadlineNs);
...
}
2.2.1 测验根据position获取ViewHolder目标
进入prefetchPositionWithDeadline
办法后,咱们终于再次见到了上一篇的老朋友——Recycler,以及熟悉的成员办法tryGetViewHolderForPositionByDeadline
:
private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
int position, long deadlineNs) {
...
RecyclerView.Recycler recycler = view.mRecycler;
RecyclerView.ViewHolder holder;
try {
...
holder = recycler.tryGetViewHolderForPositionByDeadline(
position, false, deadlineNs);
...
}
这个办法咱们在上一篇文章有介绍过,作用是测验根据position获取指定的ViewHolder目标,假如从缓存中查找不到,就会重新创立并绑定。
2.2.2 根据绑定成功与否增加到mCacheViews或RecyclerViewPool
private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
int position, long deadlineNs) {
...
if (holder != null) {
if (holder.isBound() && !holder.isInvalid()) {
// 假如绑定成功,则将该视图进入缓存
recycler.recycleView(holder.itemView);
} else {
//没有绑定,所以咱们不能缓存视图,但它会保存在池中直到下一次预取/遍历。
recycler.addViewHolderToRecycledViewPool(holder, false);
}
}
...
return holder;
}
接下来,假如顺畅地获取到了ViewHolder目标,且该ViewHolder目标现已完结数据的绑定,则下一步就该当即回收该ViewHolder目标,缓存到mCacheViews
结构中以供重用。
而假如该ViewHolder目标还未完结数据的绑定,意味着咱们没能在设定的最终期限之前完结预拉取的操作,列表项数据不完整,因而咱们不能将其缓存到mCacheViews结构中,但它会保存在mRecyclerViewPool结构中,以供下一次预拉取或重用。
预拉取机制与缓存复用机制的怎样协作的?
既然是与缓存复用机制共用相同的缓存结构,那么势必会对缓存复用机制的流程产生一定的影响,相同,让咱们用几张流程示意图来演示一下:
-
假定现在position=5的列表项的底部正好贴合到RecyclerView可见区域的底部,即还要滑动超越该列表项的外边距+分隔线高度的间隔,下一个列表项才可见。
-
跟着向上拖动的手势,GapWorker开端建议预加载的作业,根据前面整理的流程,它会提前创立并绑定position=6的列表项的ViewHolder目标,并将其缓存到mCacheViews结构中去。
- 继续保持向上拖动,当position=6的列表项行将进入屏幕时,它会依照上一篇缓存复用机制的流程,从mCacheViews结构取出可复用的ViewHolder目标,无需再次阅历创立和绑定的过程,因此滑动的流通度有了提高。
- 一起,跟着position=6的列表项进入屏幕,GapWorker也开端了对position=7的列表项的预加载
- 之后,跟着拖动间隔的增大,position=0的列表项也将被移出屏幕,增加到mCachedViews结构中去。
上一篇文章咱们讲过,mCachedViews结构的默许大小约束为2,考虑上以LinearLayoutManager为布局管理器的预拉取的状况的话则还要+1,也即一共能缓存两个被移出屏幕的可复用ViewHolder目标+一个待进入屏幕的预拉取ViewHolder目标。
不知道你们注意到没有,在过程5的示意图中,可复用ViewHolder目标是增加到预拉取ViewHolder目标前面的,之所以这姿态画是遵循了源码中的完结:
// 增加之前,先移除最老的一个ViewHolder目标
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { // 当时现已放满
recycleCachedViewAt(0); // 移除mCachedView结构中的第1个
cachedViewSize--; // 总数减1
}
// 默许从尾部增加
int targetCacheIndex = cachedViewSize;
// 处理预拉取的状况
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
// 从最终一个开端,跳过一切最近预拉取的目标排在其前面
int cacheIndex = cachedViewSize - 1;
while (cacheIndex >= 0) {
int cachedPos = mCachedViews.get(cacheIndex).mPosition;
// 增加到最近一个非预拉取的目标后边
if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
break;
}
cacheIndex--;
}
targetCacheIndex = cacheIndex + 1;
}
mCachedViews.add(targetCacheIndex, holder);
也便是说,尽管缓存复用的目标和预拉取的目标共用同一个mCachedViews结构,但二者是分组寄存的,且缓存复用的目标是排在预拉取的目标前面的。这么说或许仍是很难理解,咱们用几张示意图来演示一下就懂了:
1.假定现在mCachedViews中一起有2种类型的ViewHolder目标,黑色的代表缓存复用的目标,白色的代表预拉取的目标;
2.现在,有别的一个缓存复用的目标想要放到mCachedViews中,按源码的做法,默许会从尾部增加,即targetCacheIndex = 3:
3.随后,需求进一步承认放入的方位,它会从尾部开端逐一遍历,判别是否是预拉取的ViewHolder目标,判别的根据是该ViewHolder目标的position值是否存在mPrefetchArray结构中:
boolean lastPrefetchIncludedPosition(int position) {
if (mPrefetchArray != null) {
final int count = mCount * 2;
for (int i = 0; i < count; i += 2) {
if (mPrefetchArray[i] == position) return true;
}
}
return false;
}
4.假如是,则跳过这一项继续遍历,直到找到最近一个非预拉取的目标,将该目标的索引+1,即targetCacheIndex = cacheIndex + 1,得到承认放入的方位。
5.尽管二者是分组寄存的,但二者内部仍是有序的,即依照参加的次序正序排列。
开启预拉取机制后的实践效果怎么?
最终,咱们还剩下一个问题,即预拉取机制启用之后,对于RecyclerView的滑动展示终究能有多大的功能提高?
关于这个问题,现已有人做过相关的测试验证,这里就不再很多贴图了,只归纳一下其计划的全体思路:
- 丈量东西:开发者模式-GPU烘托模式
- 该东西以翻滚显现的直方图形式,直观地呈现烘托出界面窗口帧所需花费的时刻
- 水平轴上的每个竖条即代表一个帧,其高度则表明烘托该帧所花的时刻。
- 绿线表明的是16.67毫秒的基准线。若想保持每秒60帧的正常制作,则需确保代表每个帧的竖条保持在此线以下。
- 耗时模仿:在onBindViewHolder办法中,运用Thread.sleep(time)来模仿页面烘托的复杂度。复杂度的大小,经过time时刻的长短来体现。时刻越长,复杂度越高。
- 测试成果:比照同一复杂度下的RecyclerView滑动,未启用预拉取机制的一侧流通度显着更低,而且跟着复杂度的增加,在16ms内无法完结烘托的帧数进一步增多,延时更长,滑动卡顿更显着。
最终总结一下:
预加载机制 | |
---|---|
概念 | 使用UI线程正好处于闲暇状况的机遇,预先拉取一部分列表项视图并缓存起来,然后削减因视图创立或数据绑定等耗时操作所引起的卡顿。 |
重要类 | GapWorker:归纳滑动方向、滑动速度、与可见区域的间隔等要素,构建并调度预拉取使命列表。 |
Recycler:获取ViewHolder目标,假如缓存中找不到,则重新创立并绑定 | |
结构 | mCachedViews:顺畅获取到了ViewHolder目标,且已完结数据的绑守时放入 |
mRecyclerPool:顺畅获取到了ViewHolder目标,但还未完结数据的绑守时放入 | |
建议机遇 | 被拖动(Drag)、惯性滑动(Fling)、嵌套翻滚时 |
完结期限 | 下一个笔直同步信号宣布之前 |
少侠,请留步!若本文对你有所协助或启发,还请:
- 点赞,让更多的人能看到!
- 收藏⭐️,好文值得重复品尝!
- 关注➕,不错失每一次更文!
===> 技能号:「星际码仔」
你的支撑是我继续创造的动力,感谢!