博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
RecyclerView预加载机制源码分析
阅读量:5825 次
发布时间:2019-06-18

本文共 12716 字,大约阅读时间需要 42 分钟。

RecyclerView预加载机制分析

预加载原理简介

相关链接

原理分析

  • SDK>=21,支持RenderThread
  • UI Thread:将UI(xml,输入,动画)转化为FrameBuffer
  • RenderThread:将FrameBuffer转化为显示器显示的指令
  • 核心思想:将闲置的UI Thead利用起来,提前加载计算下一帧的FrameBuffer

预加载流程总览

  • RecyclerView
    • dragging的时候提交预加载任务
  • GapWorkder
    • 执行预加载任务,运行在主线程
  • LayoutPrefetchRegistryImpl
    • 根据touch的位置计算预加载的数量和position
  • Recycler
    • 创建ViewHolder
  • 问题
    • 什么时候开始预加载
    • 预加载几个,哪几个
    • 预加载超时怎么计算

预加载源码分析

RecyclerView.onTouchEvent

  • 在View拖动的时候通过mGapWorker.postFromTraversal,提交预加载的任务
@Override    public boolean onTouchEvent(MotionEvent e) {            case MotionEvent.ACTION_MOVE: {                              if (mScrollState != SCROLL_STATE_DRAGGING) {                                      if (mGapWorker != null && (dx != 0 || dy != 0)) {                       mGapWorker.postFromTraversal(this, dx, dy);                    }                }            } break;            }复制代码

GapWorker.postFromTraversal

void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {        if (recyclerView.isAttachedToWindow()) {            if (RecyclerView.DEBUG && !mRecyclerViews.contains(recyclerView)) {                throw new IllegalStateException("attempting to post unregistered view!");            }            // 第一次触发拖动的是否将该runnable提交到Mainhandler里面,等待UI thread执行完成再执行prefetch            if (mPostTimeNs == 0) {                mPostTimeNs = recyclerView.getNanoTime();                recyclerView.post(this);            }        }        // 后续的动作触发去更新最新的dx和dy,prefect会按照这个最新的dx和dy计算prefetch的item的position        recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);    }复制代码

GapWorker.run

@Override    public void run() {        try {            TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG);            // mRecyclerViews的原因是存在嵌套的recyclerView的情况            if (mRecyclerViews.isEmpty()) {                // abort - no work to do                return;            }            // Query most recent vsync so we can predict next one. Note that drawing time not yet            // valid in animation/input callbacks, so query it here to be safe.            final int size = mRecyclerViews.size();            long latestFrameVsyncMs = 0;            // 获取RecyclerView最进一次开始RenderThread的时间            for (int i = 0; i < size; i++) {                RecyclerView view = mRecyclerViews.get(i);                if (view.getWindowVisibility() == View.VISIBLE) {                    latestFrameVsyncMs = Math.max(view.getDrawingTime(), latestFrameVsyncMs);                }            }            if (latestFrameVsyncMs == 0) {                // abort - either no views visible, or couldn't get last vsync for estimating next                return;            }            // 计算预加载的最后时间,如果能在截止日期之前完成预加载,那么就能成功完成ViewHolder的预加载,如果不能那么就预加载失败            long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs;            prefetch(nextFrameNs);            // TODO: consider rescheduling self, if there's more work to do        } finally {            mPostTimeNs = 0;            TraceCompat.endSection();        }    }复制代码

GapWorker.prefetch

void prefetch(long deadlineNs) {        // 计算任务列表        buildTaskList();        // 开始预加载        flushTasksWithDeadline(deadlineNs);    }    复制代码
private void buildTaskList() {        // Update PrefetchRegistry in each view        final int viewCount = mRecyclerViews.size();        int totalTaskCount = 0;        for (int i = 0; i < viewCount; i++) {            RecyclerView view = mRecyclerViews.get(i);            if (view.getWindowVisibility() == View.VISIBLE) {         // 计算每一个RecyclerView预加载的数量,保存在LayoutPrefetchRegistryImpl.count里面                view.mPrefetchRegistry.collectPrefetchPositionsFromView(view, false);                totalTaskCount += view.mPrefetchRegistry.mCount;            }        }        // Populate task list from prefetch data...        mTasks.ensureCapacity(totalTaskCount);        int totalTaskIndex = 0;        for (int i = 0; i < viewCount; i++) {            RecyclerView view = mRecyclerViews.get(i);            if (view.getWindowVisibility() != View.VISIBLE) {                // Invisible view, don't bother prefetching                continue;            }            LayoutPrefetchRegistryImpl prefetchRegistry = view.mPrefetchRegistry;            final int viewVelocity = Math.abs(prefetchRegistry.mPrefetchDx)                    + Math.abs(prefetchRegistry.mPrefetchDy);            for (int j = 0; j < prefetchRegistry.mCount * 2; j += 2) {                final Task task;                if (totalTaskIndex >= mTasks.size()) {                // 针对每一个预加载的ViewHolder创建一个Task                    task = new Task();                    mTasks.add(task);                } else {                    task = mTasks.get(totalTaskIndex);                }                final int distanceToItem = prefetchRegistry.mPrefetchArray[j + 1];                task.immediate = distanceToItem <= viewVelocity;                task.viewVelocity = viewVelocity;                task.distanceToItem = distanceToItem;                task.view = view;                task.position = prefetchRegistry.mPrefetchArray[j];                totalTaskIndex++;            }        }        // ... and priority sort        Collections.sort(mTasks, sTaskComparator);    }复制代码

GapWorker.flushTasksWithDeadline

private void flushTasksWithDeadline(long deadlineNs) {// 便利所有Task开始预加载        for (int i = 0; i < mTasks.size(); i++) {            final Task task = mTasks.get(i);            if (task.view == null) {                break; // done with populated tasks            }            flushTaskWithDeadline(task, deadlineNs);            task.clear();        }    }复制代码

GapWorker.flushTaskWithDeadline

private void flushTaskWithDeadline(Task task, long deadlineNs) {        long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;        // 如果没能在deadlineNs之前构造好ViewHolder,那么次次预加载就失败了        RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,                task.position, taskDeadlineNs);        if (holder != null                && holder.mNestedRecyclerView != null                && holder.isBound()                && !holder.isInvalid()) {            prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs);        }    }复制代码

RecyclerView.tryGetViewHolderForPositionByDeadline

  • 根据dx和dy,以及当前滑动的方案计算预加载的position
  • dx和dy是通过Gapworker.postFromTraversal在滑动的时候来更新的
// 如果不能在预测的预加载时间内预加载出来,那么就不会去预加载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;                    }复制代码

RecyclerView.willCreateInTime

  • 预测的时间=当前开始执行时间+历史该Type的ViewHolder创建的平均时间
boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) {            long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs;            return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);        }复制代码

预加载哪个和具体的位置是怎么计算的

collectPrefetchPositionsFromView的调用流程图

  • 在buildTaskList的时候根据LayoutPrefetchRegistryImpl记录的滑动的位置,计算预加载的数量和位置

LayoutPrefetchRegistryImpl.collectPrefetchPositionsFromView

// nested:是否嵌套的,执行RecyclerView里面嵌套的RecyclerView的预加载void collectPrefetchPositionsFromView(RecyclerView view, boolean nested) {            mCount = 0;            if (mPrefetchArray != null) {                Arrays.fill(mPrefetchArray, -1);            }            final RecyclerView.LayoutManager layout = view.mLayout;            if (view.mAdapter != null                    && layout != null                    && layout.isItemPrefetchEnabled()) {                if (nested) {                    // nested prefetch, only if no adapter updates pending. Note: we don't query                    // view.hasPendingAdapterUpdates(), as first layout may not have occurred                    if (!view.mAdapterHelper.hasPendingUpdates()) {                        layout.collectInitialPrefetchPositions(view.mAdapter.getItemCount(), this);                    }                } else {                    // momentum based prefetch, only if we trust current child/adapter state                    if (!view.hasPendingAdapterUpdates()) {                        layout.collectAdjacentPrefetchPositions(mPrefetchDx, mPrefetchDy,                                view.mState, this);                    }                }                if (mCount > layout.mPrefetchMaxCountObserved) {                    layout.mPrefetchMaxCountObserved = mCount;                    layout.mPrefetchMaxObservedInInitialPrefetch = nested;                    view.mRecycler.updateViewCacheSize();                }            }        }复制代码

LinearLyoutManager.collectInitialPrefetchPositions

  • 计算预加载的位置
@Override    public void collectInitialPrefetchPositions(int adapterItemCount,            LayoutPrefetchRegistry layoutPrefetchRegistry) {        final boolean fromEnd;        final int anchorPos;        if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {            // use restored state, since it hasn't been resolved yet            fromEnd = mPendingSavedState.mAnchorLayoutFromEnd;            anchorPos = mPendingSavedState.mAnchorPosition;        } else {            resolveShouldLayoutReverse();            fromEnd = mShouldReverseLayout;            if (mPendingScrollPosition == NO_POSITION) {                anchorPos = fromEnd ? adapterItemCount - 1 : 0;            } else {                anchorPos = mPendingScrollPosition;            }        }        final int direction = fromEnd                ? LayoutState.ITEM_DIRECTION_HEAD                : LayoutState.ITEM_DIRECTION_TAIL;                // 计算目标预加载位置=当前方向最后一个位置+方向        int targetPos = anchorPos;        for (int i = 0; i < mInitialPrefetchItemCount; i++) {            if (targetPos >= 0 && targetPos < adapterItemCount) {                layoutPrefetchRegistry.addPosition(targetPos, 0);            } else {                break; // no more to prefetch            }            targetPos += direction;        }    }复制代码

LayoutPrefetchRegistryImpl.addPosition

  • 保存预先加载的位置到mPrefetchArray之中
@Override        public void addPosition(int layoutPosition, int pixelDistance) {            if (layoutPosition < 0) {                throw new IllegalArgumentException("Layout positions must be non-negative");            }            if (pixelDistance < 0) {                throw new IllegalArgumentException("Pixel distance must be non-negative");            }            // allocate or expand array as needed, doubling when needed            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);            }            // add position            // 数组元素2个为一组,第一保存预加载位置,第二个保存pixelDistance            mPrefetchArray[storagePosition] = layoutPosition;            mPrefetchArray[storagePosition + 1] = pixelDistance;            mCount++;        }复制代码

超时怎么计算

GapWorker.run

  • 绘制的最迟时间是下一次同步信号到来的时间
@Override    public void run() {            if (latestFrameVsyncMs == 0) {                // abort - either no views visible, or couldn't get last vsync for estimating next                return;            }            long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs;            prefetch(nextFrameNs);            // TODO: consider rescheduling self, if there's more work to do        } finally {            mPostTimeNs = 0;            TraceCompat.endSection();        }    }复制代码

Recycler.tryGetViewHolderForPositionByDeadline

if (deadlineNs != FOREVER_NS                            && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {                        // abort - we have a deadline we can't meet                        return null;                    }复制代码

RecyclerView.willCreateInTime

  • 如果预测的绘制时间晚于超时时间,那么预加载失败,直接返回
  • 预测的绘制时间=当前时间+该Type的ViewHolder历史绘制的平均时间
boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) {            long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs;            return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);        }复制代码

相关链接

  • RecyclerView预加载实测:
  • 这篇文章最全面: (看原理得看这个)

转载于:https://juejin.im/post/5b77f1d0e51d4538900586fb

你可能感兴趣的文章
作业(脚本)
查看>>
ConcurrentHashMap
查看>>
低成本高耐压20V移动电源方案IC首选钰泰ETA9870足5V2.4A输出
查看>>
win7盘符丢失的文件找回方法
查看>>
shell-3:while循环中break和continue的用法
查看>>
Java之品优购课程讲义_day13(2)
查看>>
thinkphp5验证码使用
查看>>
while 循环
查看>>
LAPH架构论坛
查看>>
ELK实时日志分部署
查看>>
移动社交助力,小程序电商的优势在哪里?
查看>>
分布式 | Dubbo 架构设计详解
查看>>
干货!MySQL 大表优化方案(1)
查看>>
jQuery思维导图梳理2
查看>>
老男孩高薪思想之做事情有多种选择才叫有能力
查看>>
修改epel源为国内地址
查看>>
程序员‘故事会’,详解负载均衡技术的实现:从tomcat到Nginx
查看>>
MongoDB简单操作
查看>>
好程序员分享大数据三大必备技能
查看>>
Linux lsof命令详解
查看>>