这是RecyclerView缓存机制系列文章的第二篇,系列文章的目录如下:

  1. RecyclerView缓存机制(咋复用?)
  2. RecyclerView缓存机制(回收些啥?)
  3. RecyclerView缓存机制(回收去哪?)
  4. RecyclerView缓存机制(scrap view)

如果想直接看结论可以移步到第四篇末尾(你会后悔的,过程更加精彩)。

上一篇文章讲述了“从哪里获得回收的表项”,这一篇会结合实际回收场景分析下“回收哪些表项?”。

(ps: 下文中的 粗斜体字 表示引导源码阅读的内心戏)

回收场景

在众多回收场景中最显而易见的就是“滚动列表时移出屏幕的表项被回收”。滚动是由MotionEvent.ACTION_MOVE事件触发的,就以RecyclerView.onTouchEvent()为切入点寻觅“回收表项”的时机

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {@Overridepublic boolean onTouchEvent(MotionEvent e) {...case MotionEvent.ACTION_MOVE: {...if (scrollByInternal(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0,vtev)) {getParent().requestDisallowInterceptTouchEvent(true);}...}} break;...}
}
复制代码

去掉了大量位移赋值逻辑后,一个处理滚动的函数出现在眼前:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {...@VisibleForTesting LayoutManager mLayout;...boolean scrollByInternal(int x, int y, MotionEvent ev) {...if (mAdapter != null) {...if (x != 0) {consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);unconsumedX = x - consumedX;}if (y != 0) {consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);unconsumedY = y - consumedY;}...}...
}
复制代码

RecyclerView把滚动交给了LayoutManager来处理,于是移步到最熟悉的LinearLayoutManager

public class LinearLayoutManager extends RecyclerView.LayoutManager implements ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {...@Overridepublic int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,RecyclerView.State state) {if (mOrientation == HORIZONTAL) {return 0;}return scrollBy(dy, recycler, state);}...int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {if (getChildCount() == 0 || dy == 0) {return 0;}mLayoutState.mRecycle = true;ensureLayoutState();final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;final int absDy = Math.abs(dy);//更新LayoutState(这个函数对于“回收哪些表项”来说很关键,待会会提到)updateLayoutState(layoutDirection, absDy, true, state);//滚动时向列表中填充新的表项final int consumed = mLayoutState.mScrollingOffset+ fill(recycler, mLayoutState, state, false);if (consumed < 0) {if (DEBUG) {Log.d(TAG, "Don't have any more elements to scroll");}return 0;}final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;mOrientationHelper.offsetChildren(-scrolled);if (DEBUG) {Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);}mLayoutState.mLastScrollDelta = scrolled;return scrolled;}...
}
复制代码

沿着调用链往下找,发现了一个上一篇中介绍过的函数LinearLayoutManager.fill(),原来列表滚动的同时也会不断的向其中填充表项( 想想也是,不然怎么会不断有新的表项出现呢~ )。上一遍只关注了其中填充的逻辑,但其实里面还有回收逻辑:

public class LinearLayoutManager extends RecyclerView.LayoutManager implements ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {...int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {...int remainingSpace = layoutState.mAvailable + layoutState.mExtra;LayoutChunkResult layoutChunkResult = mLayoutChunkResult;//不断循环获取新的表项用于填充,直到没有填充空间while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {layoutChunkResult.resetInternal();if (VERBOSE_TRACING) {TraceCompat.beginSection("LLM LayoutChunk");}//填充新的表项layoutChunk(recycler, state, layoutState, layoutChunkResult);if (VERBOSE_TRACING) {TraceCompat.endSection();}if (layoutChunkResult.mFinished) {break;}layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null|| !state.isPreLayout()) {layoutState.mAvailable -= layoutChunkResult.mConsumed;// we keep a separate remaining space because mAvailable is important for recyclingremainingSpace -= layoutChunkResult.mConsumed;}if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {//在当前滚动偏移量基础上追加因新表项插入增加的像素(这句话对于“回收哪些表项”来说很关键,待会会提到)layoutState.mScrollingOffset += layoutChunkResult.mConsumed;if (layoutState.mAvailable < 0) {layoutState.mScrollingOffset += layoutState.mAvailable;}//回收表项recycleByLayoutState(recycler, layoutState);}if (stopOnFocusable && layoutChunkResult.mFocusable) {break;}...}...return start - layoutState.mAvailable;}
}
复制代码

在不断获取新表项用于填充的同时也在回收表项(想想也是,列表滚动的时候有表项插入的同时也有表项被移出),移步到回收表项的函数:

public class LinearLayoutManager extends RecyclerView.LayoutManager implements ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {...private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {if (!layoutState.mRecycle || layoutState.mInfinite) {return;}if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);} else {recycleViewsFromStart(recycler, layoutState.mScrollingOffset);}}.../*** Recycles views that went out of bounds after scrolling towards the end of the layout.* 当向列表尾部滚动时回收滚出屏幕的表项* <p>* Checks both layout position and visible position to guarantee that the view is not visible.** @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}* @param dt       This can be used to add additional padding to the visible area. This is used*                 to detect children that will go out of bounds after scrolling, without*                 actually moving them.(该参数被用于检测滚出屏幕的表项)*/private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {if (dt < 0) {if (DEBUG) {Log.d(TAG, "Called recycle from start with a negative value. This might happen"+ " during layout changes but may be sign of a bug");}return;}// ignore padding, ViewGroup may not clip children.final int limit = dt;final int childCount = getChildCount();if (mShouldReverseLayout) {for (int i = childCount - 1; i >= 0; i--) {View child = getChildAt(i);if (mOrientationHelper.getDecoratedEnd(child) > limit|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {// stop hererecycleChildren(recycler, childCount - 1, i);return;}}} else {//遍历LinearLayoutManager的孩子找出其中应该被回收的for (int i = 0; i < childCount; i++) {View child = getChildAt(i);if (mOrientationHelper.getDecoratedEnd(child) > limit|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {// stop here//回收索引为0到i-1的表项recycleChildren(recycler, 0, i);return;}}}}...
}
复制代码

原来RecyclerView的回收分两个方向:1. 从列表头回收 2.从列表尾回收。就以“从列表头回收”为研究对象分析下RecyclerView在滚动时到底是怎么判断“哪些表项应该被回收?”。 (“从列表头回收表项”所对应的场景是:手指上滑,列表向下滚动,新的表项逐个插入到列表尾部,列表头部的表项逐个被回收。)

回收哪些表项

要回答这个问题,刚才那段代码中套在recycleChildren(recycler, 0, i)外面的判断逻辑是关键:mOrientationHelper.getDecoratedEnd(child) > limit

/*** Helper class for LayoutManagers to abstract measurements depending on the View's orientation.* 该类用于帮助LayoutManger抽象出基于视图方向的测量* <p>* It is developed to easily support vertical and horizontal orientations in a LayoutManager but* can also be used to abstract calls around view bounds and child measurements with margins and* decorations.** @see #createHorizontalHelper(RecyclerView.LayoutManager)* @see #createVerticalHelper(RecyclerView.LayoutManager)*/
public abstract class OrientationHelper {.../*** Returns the end of the view including its decoration and margin.* <p>* For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right* decoration and 3px right margin, returned value will be 205.** @param view The view element to check* @return The last pixel of the element* @see #getDecoratedStart(android.view.View)*/public abstract int getDecoratedEnd(View view);...public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {return new OrientationHelper(layoutManager) {...@Overridepublic int getDecoratedEnd(View view) {final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)view.getLayoutParams();return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;}...
}
复制代码

结合注释和该方法的实现,原来mOrientationHelper.getDecoratedEnd(child)表示当前表项的尾部相对于列表头部的坐标,OrientationHelper这层抽象屏蔽了列表的方向,所以这句话在纵向列表中可以翻译成“当前表项的底部相对于列表顶部的纵坐标”。

判断条件mOrientationHelper.getDecoratedEnd(child) > limit中的limit又是什么鬼?在纵向列表中,“表项底部纵坐标 > 某个值”意味着表项位于某条线的下方,回看一眼“回收表项”的逻辑:

//遍历LinearLayoutManager的孩子找出其中应该被回收的
for (int i = 0; i < childCount; i++) {View child = getChildAt(i);//直到表项底部纵坐标大于某个值后,回收该表项以上的所有表项if (mOrientationHelper.getDecoratedEnd(child) > limit|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {// stop here//回收索引为0到索引为i-1的表项recycleChildren(recycler, 0, i);return;}
}
复制代码

隐约觉得limit应该等于0,这样不正好是回收所有从列表头移出的表项吗? 不知道这样YY到底对不对,还是沿着调用链向上找一下limit被赋值的地方吧~,调用链很长,就不全部罗列了,但其中有两个关键点,其实我在上面的代码中埋了伏笔,现在再罗列一下:

public class LinearLayoutManager extends RecyclerView.LayoutManager implements ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {   ...int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {if (getChildCount() == 0 || dy == 0) {return 0;}mLayoutState.mRecycle = true;ensureLayoutState();final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;final int absDy = Math.abs(dy);//1. 更新LayoutState(这个函数对于“回收哪些表项”来说很关键,待会会提到)updateLayoutState(layoutDirection, absDy, true, state);//滚动时向列表中填充新的表项final int consumed = mLayoutState.mScrollingOffset+ fill(recycler, mLayoutState, state, false);...}...int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {...//不断循环获取新的表项用于填充,直到没有填充空间while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {...if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {//2. 在当前滚动偏移量基础上追加因新表项插入增加的像素(这句话对于“回收哪些表项”来说很关键,待会会提到)layoutState.mScrollingOffset += layoutChunkResult.mConsumed;if (layoutState.mAvailable < 0) {layoutState.mScrollingOffset += layoutState.mAvailable;}//回收表项recycleByLayoutState(recycler, layoutState);}...}...return start - layoutState.mAvailable;}...private void updateLayoutState(int layoutDirection, int requiredSpace,boolean canUseExistingSpace, RecyclerView.State state) {...int scrollingOffset;if (layoutDirection == LayoutState.LAYOUT_END) {mLayoutState.mExtra += mOrientationHelper.getEndPadding();//获得当前方向上里列表尾部最近的孩子(最后一个孩子)final View child = getChildClosestToEnd();// the direction in which we are traversing childrenmLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD: LayoutState.ITEM_DIRECTION_TAIL;mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);// calculate how much we can scroll without adding new children (independent of layout)// 获得一个滚动偏移量,如果只滚动了这个数值那不需要添加新的孩子scrollingOffset = mOrientationHelper.getDecoratedEnd(child)- mOrientationHelper.getEndAfterPadding();} else {...}...//对mLayoutState.mScrollingOffset赋值mLayoutState.mScrollingOffset = scrollingOffset;}
}
复制代码

一图胜千语:

关于limit等于0的YY破灭了,其实limit是一根横谓语列表中间的横线,它的值表示这一次滚动的总距离。(图中是一种理想情况,即当滚动结束后新插入表项的底部正好和列表底部重叠)其实 回收表项的时机是在滚动真正发生之前,此时我们预先计算出滚动的偏移量,根据偏移量筛选出滚动发生后应该被删除的表项。即 limit这根线也可以表述为:当滚动发生后,列表当前 limit这个位置会成为列表的头部

分析完“回收哪些表项”后,一不小心发现篇幅有点长了,那关于回收去哪里?将放到下一篇在讲。

RecyclerView缓存机制(回收些啥?)相关推荐

  1. RecyclerView缓存机制(scrap view)

    这是RecyclerView缓存机制系列文章的第四篇,系列文章的目录如下: RecyclerView缓存机制(咋复用?) RecyclerView缓存机制(回收些啥?) RecyclerView缓存机 ...

  2. RecyclerView缓存机制

    1. ViewHolder 1.1 作用 ViewHolder是对RecyclerView上的ItemView的封装,它是RecyclerView缓存的载体.它封装了以下属性: View itemVi ...

  3. RecyclerView 缓存机制

    RecyclerView 中缓存同意交给内部的一个叫Recycler的类来管理. 分四级 Scrap Cache ViewCacheExtension RecyclerViewPool 它缓存的是Vi ...

  4. 【腾讯Bugly干货分享】Android ListView与RecyclerView对比浅析--缓存机制

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/5811d... 作者:黄宁源 一,背景 RecyclerView是谷歌官方出的一 ...

  5. recyclerview item点击无效_让你彻底掌握RecyclerView的缓存机制

    点击上方蓝字关注 ?? 来源:肖邦kakahttps://www.jianshu.com/p/3e9aa4bdaefd 前言 RecyclerView这个控件几乎所有的Android开发者都使用过(甚 ...

  6. RecyclerView详解一,使用及缓存机制

    本文大致会先讲解RecyclerView的基础知识及使用,最后会深入讲解一点原理.当然,本人知识水平有限哈,太深入的东西我现在还没接触到,还请大家包容,阿里嘎多~ 一.RecyclerView的历史与 ...

  7. 一篇文章搞定《RecyclerView缓存复用机制》

    一篇文章搞定<RecyclerView缓存复用机制> 前言 零.为什么要缓存 一.RecyclerView如何构建我们的列表视图 二.缓存过程 三.缓存结构 1.mChangedScrap ...

  8. Android Glide图片加载框架(三)缓存机制

    文章目录 一.缓存简介 二.缓存用法 内存缓存方式 磁盘缓存方式 三.缓存KEY 四.内存缓存 内存缓存流程 五.磁盘缓存 磁盘缓存流程 Android Glide图片加载框架系列文章 Android ...

  9. MyBatis复习笔记6:MyBatis缓存机制

    MyBatis缓存机制 MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制.缓存可以极大的提升查询效率. MyBatis系统中默认定义了两级缓存. 一级缓存和二级缓存. 默认情 ...

最新文章

  1. 跟着Rocskdb 学 存储引擎:读写链路的代码极致优化
  2. 协议分析中的TCP/IP网络协议
  3. R语言ggplot2可视化图例放置在图像底部(bottom)并分两行显示实战
  4. Ajax操作的四个步骤
  5. 多任务学习有用的资料
  6. 基于Android系统开发的简易音乐播放器
  7. 【异或交换原理】按位进行异或操作,实现数的交换
  8. WCF 使用证书认证 方法
  9. 如何扩展Laravel Auth来满足项目需求
  10. FFmpeg AVFMT_NOFILE宏定义剖析
  11. 【人脸识别】基于matlab GUI LBP人脸识别【含Matlab源码 1282期】
  12. 【1800题】一、函数、极限、连续
  13. python opencv实现 12色相环、24色相环(基于RGB空间和基于HSV空间实现)
  14. php脉聊交友源码_脉聊源码-PHP脉聊交友网站源码(附app源码)下载-西西软件下载...
  15. 支付宝APP支付集成文档
  16. 用C++实现魔方并输出步骤
  17. 电话交换机tdmx2000dx硬件配置说明
  18. 如何知道电脑开机记录?
  19. 高性能分布式执行框架——Ray
  20. QT5实现串口收发数据(上位机与下位机通信)

热门文章

  1. pythonurllib模块-Python3中核心模块urllib的用法大全
  2. 数据结构 python的书推荐-java数据结构书一般推荐看什么好?
  3. javascript高级程序设计之基本概念
  4. LeetCode Maximal Square(最大子矩阵)
  5. 基于FCN的图像语义分割
  6. Linux 利用busybox制作根文件系统
  7. 【手把手】JavaWeb 入门级项目实战 -- 文章发布系统 (第九节)
  8. 网络干货,无论是运维还是开发都要知道的网络知识系列之(五)
  9. 《高可用MySQL》2 – 单机版MySQL主从配置
  10. 我理解的session和cookie