什么是预取
预取就是界面没有展示出来的元素,是下一个即将要展示出来的元素,比如界面展示的是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);
}
他做的事情可分为两点:
- 计算需要预取的位置;
- 新建需要预取的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的预取机制整个的过了一遍,最后在总结一下:
总结
- 预取功能是在21及以上版本才有的,这个功能可以通过LayoutManager的setItemPrefetchEnabled(boolean enabled)去关闭的;
- 如果RecyclerView中还有子RecyclerView(并且是用LinearLayoutManager),那么子RecyclerView可以通过LinearLayoutManager的setInitialPrefetchItemCount()去设置预取的个数;
- 预取功能开启,滑动时,会先去计算下一个需要显示的item,如果计算到下一个item在下一帧中一定会显示出来,那么就一定会取出来,如果下一帧不会显示,会根据下一帧到来的时间看是否已经取出, 如果没取出那么就会预取失败,取出了就会放到RecyclerView中的第二级缓存中去,第二级缓存是不需要重新绑定的,这样就可以减少下一帧时间。