掌握这17张图,没人比你更懂RecyclerView的预加载

手机软件开发 2024-9-23 00:16:47 33 0 来自 中国
回首上一篇文章,我们为了镌汰形貌标题的维度,于演示之前附加了许多限定条件,比如禁用了RecyclerView的预拉取机制。
现实上,预拉取(prefetch)机制作为RecyclerView的告急特性之一,常常与缓存复用机制一起共同利用、共同协作,极大地提拔了RecyclerView团体滑动的流畅度。
而且,这种特性在ViewPager2中同样得以生存,对ViewPager2滑动结果的出现也起着关键性的作用。因此,我们ViewPager2系列的第二篇,就是要来偏重先容RecyclerView的预拉取机制。
预拉取是指什么?

在盘算机术语中,预拉取指的是在已知必要某部分数据的条件下,利用体系资源闲置的空档,预先拉取这部分数据到本地,从而进步实行时的效率。
详细到RecyclerView预拉取的情境则是:

  • 利用UI线程恰长处于空闲状态的时机

  • 预先拉取待进入屏幕地域内的一部分列表项视图并缓存起来

  • 从而镌汰因视图创建或数据绑定等耗时操纵所引起的卡顿。
3.png 预拉取是怎么实现的?

正如把缓存复用的现实工作委托给了其内部的Recycler类一样,RecyclerView也把预拉取的现实工作委托给了一个名为GapWorker的类,其内部的工作流程,可以用以下这张头脑导图来概括:
接下来我们就循着这张头脑导图,来一一拆解预拉取的工作流程。
1.发起预拉取工作

通过查找对GapWorker对象的引用,我们可以梳理出3个发起预拉取工作的时机,分别是:

  • RecyclerView被拖动(Drag)时
5.gif     @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渲染工作,使得主线程可以更加专注于与用户的交互,从而大幅进步页面的流畅度。
7.png 但这里有一个标题。
当UI线程提前完成工作,并将一个帧通报给RenderThread渲染之后,就会进入所谓的休眠状态,出现了大量的空闲时间,直至下一帧开始绘制之前。如图所示:
8.png 一方面,这些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:指的是由列表项的底部位置,加上列表项设立的外边距,再加上列表项隔断的高度盘算得到的值。
我们用一张图来分析一下:
11.png 起首,图中的①表现一个完备的屏幕可见解区,此中:

  • 深灰色地域对应的是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结构中去。
13.png

  • 继承保持向上拖动,当position=6的列表项即将进入屏幕时,它会按照上一篇缓存复用机制的流程,从mCacheViews结构取出可复用的ViewHolder对象,无需再次履历创建和绑定的过程,因此滑动的流畅度有了提拔。
14.png

  • 同时,随着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:
17.png 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 == position) return true;            }        }        return false;    } 18.png 4.假如是,则跳过这一项继承遍历,直到找到迩来一个非预拉取的对象,将该对象的索引+1,即targetCacheIndex = cacheIndex + 1,得到确认放入的位置。
5.固然二者是分组存放的,但二者内部还是有序的,即按照参加的序次正序分列。
开启预拉取机制后的现实结果怎样?

末了,我们还剩下一个标题,即预拉取机制启用之后,对于RecyclerView的滑动展示毕竟能有多大的性能提拔?
关于这个标题,已经有人做过相干的测试验证,这里就不再大量贴图了,只概括一下其方案的团体思绪:

  • 测量工具:开辟者模式-GPU渲染模式



    • 该工具以滚动表现的直方图情势,直观地出现渲染出界面窗口帧所需耗费的时间
    • 程度轴上的每个竖条即代表一个帧,其高度则表现渲染该帧所花的时间。
    • 绿线表现的是16.67毫秒的基准线。若想维持每秒60帧的正常绘制,则需包管代表每个帧的竖条维持在此线以下。

  • 耗时模拟:在onBindViewHolder方法中,利用Thread.sleep(time)来模拟页面渲染的复杂度。复杂度的巨细,通过time时间的是非来表现。时间越长,复杂度越高。
  • 测试结果:对比同一复杂度下的RecyclerView滑动,未启用预拉取机制的一侧流畅度显着更低,而且随着复杂度的增长,在16ms内无法完成渲染的帧数进一步增多,延时更长,滑动卡顿更显着。

    21.png
末了总结一下:
预加载机制概念利用UI线程恰长处于空闲状态的时机,预先拉取一部分列表项视图并缓存起来,从而镌汰因视图创建或数据绑定等耗时操纵所引起的卡顿。告急类GapWorker:综合滑动方向、滑动速率、与可见解区的间隔等要素,构建并调理预拉取使命列表。Recycler:获取ViewHolder对象,假如缓存中找不到,则重新创建并绑定结构mCachedViews:顺遂获取到了ViewHolder对象,且已完成数据的绑定时放入mRecyclerPool:顺遂获取到了ViewHolder对象,但还未完成数据的绑定时放入发起时机被拖动(Drag)、惯性滑动(Fling)、嵌套滚动时完成限期下一个垂直同步信号发出之前
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 20:23, Processed in 0.180914 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表