RecyclerView(一):预取机制

什么是预取

预取就是界面没有展示出来的元素,是下一个即将要展示出来的元素,比如界面展示的是10条目,那么当你滑动的时候,那么即将展示的将是第11个(还没显示出来),这时候RecyclerView就会提前将需要展示的第一个元素缓存到mCachedViews中(RecyclerView四级缓存中的第二级缓存,后面会单独提取一篇讲缓存),mCachedViews中缓存的view是不要重新绑定的,也就说不会执行adapter的onBindViewHolder()方法,这个功能是在版本21之后加的。

预取原理实现

预取原理实现主要涉及到的是GapWorker这个类,先来看下这个类的初始化,在RecyclerView的onAttachedToWindow()中:

    private static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21;
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        //对当前版本进行判断,
        if (ALLOW_THREAD_GAP_WORK) {
            // Register with gap worker
            //这里利用的是ThreadLocal的特性,这也说明主线程中就一个GapWorker实例对象 
            mGapWorker = GapWorker.sGapWorker.get();
            if (mGapWorker == null) {
                mGapWorker = new GapWorker();

                // break 60 fps assumption if data from display appears valid
                // NOTE: we only do this query once, statically, because it's very expensive (> 1ms)
                Display display = ViewCompat.getDisplay(this);
                float refreshRate = 60.0f;
                if (!isInEditMode() && display != null) {
                    float displayRefreshRate = display.getRefreshRate();
                    if (displayRefreshRate >= 30.0f) {
                        refreshRate = displayRefreshRate;
                    }
                }
                //计算绘制一帧所需时间,单位是ns
                mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate);
                GapWorker.sGapWorker.set(mGapWorker);
            }
            //将RecyclerView添加到RecyclerView中
            mGapWorker.add(this);
        }
    }

整个逻辑还是很清晰,就是初始化一个GapWorker并保存到ThreadLocal中。创建之后,接下来就是对这个类的使用了,预取机制主要是在滑动的时候,那就去RecyclerView的onTouchEvent中MOVE事件中查找了,可以看到其中有这样一段代码:

	if (mGapWorker != null && (dx != 0 || dy != 0)) {
           mGapWorker.postFromTraversal(this, dx, dy);
    }

这段代码是在请求绘制界面之后调用的,也就说,在请求绘制当前帧的时候,会提前缓存下一帧的view,现在就跟着postFromTraversal()这个方法往下走:

    void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
    	//为true
        if (recyclerView.isAttachedToWindow()) {
        //mRecyclerViews中包含的是已经绑定到Window上的所有RecyclerView,不止是当前处在前台的activity
        //可以这么理解,只要activity中含有RecyclerView,并且没有被销毁,那么这个RecyclerView就会被添加到mRecyclerViews中
            if (RecyclerView.DEBUG && !mRecyclerViews.contains(recyclerView)) {
                throw new IllegalStateException("attempting to post unregistered view!");
            }
            if (mPostTimeNs == 0) {
                mPostTimeNs = recyclerView.getNanoTime();
                //预取的逻辑是通过这里处理的,GapWorker实现了Runnable接口
                recyclerView.post(this);
            }
        }
		//这里只是将这两只传递进去,就是赋值而已
        recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
    }

下面就来看它的run方法中做了什么操作:

    @Override
    public void run() {
        try {
            TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG);

            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,找个当前处于可见状态的view并获取上一帧的时间
            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;
            }
			//计算下一帧到来的时间,在这个时间内没有预取到那么就会预取失败,预取的本意就是为了滑动更流畅,如果预取在
			//下一帧到来时还没取到,还去取的话那么就会影响到绘制,得不偿失,
            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();
        }
    }

	    void prefetch(long deadlineNs) {
        buildTaskList();
        flushTasksWithDeadline(deadlineNs);
    }

可以看到prefetch()中有两个方法,先来看第一个buildTaskList():

    private void buildTaskList() {
        // Update PrefetchRegistry in each view
        final int viewCount = mRecyclerViews.size();
        int totalTaskCount = 0;
        //计算有多少个可见的RecyclerView
        for (int i = 0; i < viewCount; i++) {
            RecyclerView view = mRecyclerViews.get(i);
            if (view.getWindowVisibility() == View.VISIBLE) {
            //计算需要预取条目的位置,最终会调用到addPosition(),将位置信息保存到mPrefetchArray数组中
                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);
             //创建预取条目的task
            //mCount是当前预取位置的个数(比如当前可见的RecyclerView有两个,那么mCount就为2),这里*2是因为保存位置
            //的数组不仅保存了位置,还保存了到预取位置的距离
            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);
                }
                //当前可见item到预取位置的距离
                final int distanceToItem = prefetchRegistry.mPrefetchArray[j + 1];
				//表示这个预取的item在下一帧是否会显示,通常为false,表示在下一帧不显示,为true就说明在下一帧是会显示的
                task.immediate = distanceToItem <= viewVelocity;
                //滑动的距离
                task.viewVelocity = viewVelocity;
                //到预取位置的距离
                task.distanceToItem = distanceToItem;
                //预取item的RecyclerView
                task.view = view;
                //预取item所处的位置(position)
                task.position = prefetchRegistry.mPrefetchArray[j];
				//预取的总个数
                totalTaskIndex++;
            }
        }

        // ... and priority sort
        //对需要预取的task进行排序,immediate =true的将会排在前面,这是因为immediate =true的将会在下一帧显示
        Collections.sort(mTasks, sTaskComparator);
    }

他做的事情可分为两点:

  1. 计算需要预取的位置;
  2. 新建需要预取的task,里面包含了需要预取的相关信息;
    重新回到上面的prefetch()方法,现在来看他的第二个方法flushTasksWithDeadline(long deadlineNs):
    private void flushTasksWithDeadline(long deadlineNs) {
    //所有的task,预取出相应的view,然后清空task
        for (int i = 0; i < mTasks.size(); i++) {
            final Task task = mTasks.get(i);
            if (task.view == null) {
                break; // done with populated tasks
            }
            //这里就是去取task
            flushTaskWithDeadline(task, deadlineNs);
            task.clear();
        }
    }

主要逻辑交给了flushTaskWithDeadline()去执行:

    private void flushTaskWithDeadline(Task task, long deadlineNs) {
        long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
        RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
                task.position, taskDeadlineNs);
        if (holder != null
                && holder.mNestedRecyclerView != null
                && holder.isBound()
                && !holder.isInvalid()) {
            prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs);
        }
    }

看名字就知道prefetchPositionWithDeadline()是去预取view,然后返回的就是ViewHolder,ViewHolder不为null就说明预取成功了,下面还一个判断执行,这个判断的作用是预取的这个view是否是RecyclerView,如果是那么就会接着去预取,这里就只去看prefetchPositionWithDeadline()这个方法了:

    private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
            int position, long deadlineNs) {
        if (isPrefetchPositionAttached(view, position)) {
            // don't attempt to prefetch attached views
            return null;
        }
		//这里先拿到RecyclerView的缓存对象
        RecyclerView.Recycler recycler = view.mRecycler;
        RecyclerView.ViewHolder holder;
        try {
            view.onEnterLayoutOrScroll();
            //这里就是去缓存中获取或是新创建一个,这里先不讲,之后分析RecyclerView缓存实现的时候会说到
            holder = recycler.tryGetViewHolderForPositionByDeadline(
                    position, false, deadlineNs);

            if (holder != null) {
                if (holder.isBound() && !holder.isInvalid()) {
                    // Only give the view a chance to go into the cache if binding succeeded
                    // Note that we must use public method, since item may need cleanup
                    //一般会执行到这里,这里是将获取的view添加到第二级缓存mCachedViews中
                    recycler.recycleView(holder.itemView);
                } else {
                    // Didn't bind, so we can't cache the view, but it will stay in the pool until
                    // next prefetch/traversal. If a View fails to bind, it means we didn't have
                    // enough time prior to the deadline (and won't for other instances of this
                    // type, during this GapWorker prefetch pass).
                    //将holder添加到第四级缓存mRecyclerPool中
                    recycler.addViewHolderToRecycledViewPool(holder, false);
                }
            }
        } finally {
            view.onExitLayoutOrScroll(false);
        }
        return holder;
    }

到这里就将RecyclerView的预取机制整个的过了一遍,最后在总结一下:

总结
  1. 预取功能是在21及以上版本才有的,这个功能可以通过LayoutManager的setItemPrefetchEnabled(boolean enabled)去关闭的;
  2. 如果RecyclerView中还有子RecyclerView(并且是用LinearLayoutManager),那么子RecyclerView可以通过LinearLayoutManager的setInitialPrefetchItemCount()去设置预取的个数;
  3. 预取功能开启,滑动时,会先去计算下一个需要显示的item,如果计算到下一个item在下一帧中一定会显示出来,那么就一定会取出来,如果下一帧不会显示,会根据下一帧到来的时间看是否已经取出, 如果没取出那么就会预取失败,取出了就会放到RecyclerView中的第二级缓存中去,第二级缓存是不需要重新绑定的,这样就可以减少下一帧时间。
  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值