Android进阶学习(9)-- RecyclerView回收、复用源码浅析

RecyclerView复用、回收源码浅析

      • 前言
      • RecyclerView中的容器
      • 回收源码流程
      • 复用源码流程
      • 总结

前言

RecyclerView,可以理解为是谷歌工程师写的一个自定义View开源给我们用,相比于ListView我个人感觉最大的优点在于代码模块化,RecyclerView源码中的封装的非常到位,将各个功能模块化、解耦,阅读源码、使用起来都更加容易;RecyclerView当然也存在缺点,使用时可能会出现错乱,尤其是网络请求加载的数据、图片,发生这个问题的主要原因就是它的复用机制,需要通过跟踪源码来搞明白它的复用和回收机制;

//下面的源码都是基于androidx.recyclerview1.1.0版本源码分析的
implementation 'androidx.recyclerview:recyclerview:1.1.0'

RecyclerView中的容器

了解回收、复用的流程前,先了解下 RecyclerView 源码中的一些容器:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第1张图片
这是RecyclerView 源码的部分结构,可以看出每个功能划分的很细致;其中的 Recycler 就是负责回收的内部类:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第2张图片
这是Recycler 里面定义的一些容器,我们逐个来认识一下:
mAttachedScrap 未与RecyclerView分离的ViewHolder列表,也就是当前屏幕上可见的
mChangedScrap 存储 notifXXX 方法时需要改变的 ViewHolder
mCachedViews 这个容器是重点,复用机制优先从mCachedViews中找缓存,它的默认大小是 2 ;
mUnmodifiableAttachedScrap 暂时没找到它的用途,在复用机制中也没有用到
mRecyclerPool 这个也是重点,当mCachedViews中没有缓存,就从mRecyclerPool中去找,这个缓存池中会缓存所有种类的ViewHolder各5个;
mViewCacheExtension 用于我们 自定义缓存策略使用

回收源码流程

看源码前需要认真思考从哪里入手去看,需要了解它回收、复用的源码,那么就应该从发生回收、复用的时候去入手,也就是当我们滑动RV的时候;滑动,在Android自定义View中触发的对应事件是onTouchEvent的ACTION_MOVE,所以直接从RV中的onTouchEvent方法看起:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第3张图片
直接去看 ACTION_MOVE:
在这里插入图片描述
在 ACTION_MOVE 的分支中会看到这样的代码:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第4张图片
我们点进去这个方法:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第5张图片
接着点进去scrollStep:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第6张图片
以垂直滑动为例,我们点进去scrollVerticallyBy :
在这里插入图片描述
会发现只返回了一个 0 ,上面也说了,RV的优点就是把代码模块化,这个 scrollVerticallyBy 是内部类 LayoutManager 里面的一个方法; LayoutManager,我们每次用 RV 都需 setLayoutManager,所以,我们应该去看LayoutManager 实现类中的 scrollVerticallyBy :
在这里插入图片描述
这里就以经常使用的 LinearLayoutManager 为例,我们打开LinearLayoutManager类:
在这里插入图片描述
确实实现了 RecyclerView.LayoutManager , 我们搜一下 scrollVerticallyBy 这个方法:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第7张图片
它 return 了一个 scrollBy 继续点进去:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第8张图片
注意这个不起眼的 fill 方法,这个 fill 方法里是处理回收和复用的一些调用:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第9张图片
recycleByLayoutState 是处理回收的方法,layoutChunk 是处理复用的方法;下面复用源码流程就直接从 layoutChunk 这个方法开始,我们先来看 recycleByLayoutState 回收的方法:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第10张图片
向下滑动需要回收头部的item,向上滑动则回收尾部item,这里以向下滑动为例,点进去recycleViewsFromStart 方法:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第11张图片
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第12张图片
点进去 recycleChildren 方法:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第13张图片
接着点进去 removeAndRecycleViewAt 方法:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第14张图片
继续跟踪 recycleView 方法:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第15张图片
recycleViewHolderInternal 方法中,是真正处理回收逻辑的代码:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第16张图片
首先判断了无法回收的情况,接着往下看:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第17张图片
回收时会先判断mCachedViews中是否存满,它最多存两个缓存,如果它里面存满了,最先进去的item缓存会被放到缓存池中,先点进去 recycleCachedViewAt 看他的逻辑:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第18张图片
addViewHolderToRecycledViewPool 是向缓存池中加入item的方法,我们点进去看:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第19张图片
点进去putRecycledView 中:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第20张图片
缓存池的结构是一个SparseArray,相同的viewholder 它会用一个数组放起来,通过viewholder 的 type 去取同一类型的数组,数组中最多存放五个,如果取出来数组发现已经有5个了,则直接放弃需要缓存的item;

这是第一种回收情况,优先放在mCacheView中;我们回到回收方法recycleViewHolderInternal中看剩下的代码:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第21张图片
如果,item 已经 缓存到mCacheView中 则不会执行下面的if分支;如果一个item不符合mCachedViews回收条件,则进入下面的if语句,调用 addViewHolderToRecycledViewPool;

复用源码流程

了解了回收的流程,接着跟踪复用的源码,复用的触发时机同样是发生滑动的时候,上面也说到了,fill 方法中 会分别调用回收 和 复用方法,我们直接从 fill 方法开始跟踪:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第22张图片
我们点进去layoutChunk方法中:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第23张图片
首先会执行 layoutState.next(recycler),点进去next方法:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第24张图片
这里又调用到了Recycler 中的方法,点进去getViewForPosition :
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第25张图片
tryGetViewHolderForPositionByDeadline方法就是处理复用的方法,看一下它的处理逻辑:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第26张图片
当滚动时,不会直接创建新的item,会先从一级缓存,也就是mChangeScrap(数据变化会从这里找),mAttachedScrap,mCacheViews,我们点进去getScrapOrHiddenOrCachedHolderForPosition看一下逻辑:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第27张图片
优先找的是mAttachedScrap,接着往下看:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第28张图片
看完这里的代码,我们继续回到tryGetViewHolderForPositionByDeadline方法中:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第29张图片
刚刚第一步执行的是getScrapOrHiddenOrCachedHolderForPosition,而这个是getScrapOrCachedViewForId,通过名字可以看出,两个方法一个是根据position去找,一个是根据 id 去找,内部的逻辑是相似的 都是从 mAttachedScrap,mCacheViews 中去寻找,这里就不放图了,接着往下看:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第30张图片
一级缓存找完,就开始找二级缓存也就是我们自定义的mViewCacheExtension,一般我们不设置这个,也就直接跳过了,接着往下看:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第31张图片
一级缓存和二级缓存都没有找到,就会到缓存池中去寻找了,点进去getRecycledView方法:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第32张图片
如果找到则返回对应的试图,找不到则返回null,回到方法中,接着往下看:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第33张图片
当三级缓存都找完了,依然没有可以复用的item,则调用Adapter中的createViewHolder方法,创建新的视图,接着往下看找到可以复用的item的逻辑:
Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第34张图片
找到的holder 没有绑定 则调用tryBindViewHolderByDeadline进行绑定,我们进去tryBindViewHolderByDeadline方法看一下:Android进阶学习(9)-- RecyclerView回收、复用源码浅析_第35张图片
整体的复用流程就是这样

总结

回收:item回收时,根据条件判断优先放入mCacheViews中进行缓存,mCacheViews默认最多存放两个,采取先进先出的策略;如果不满足放入mCacheViews的条件,则放入缓存池RecycledViewPool中,缓存池是一个SparseArray类型,同一类型的item数组默认做多存放五个,如果已经存在五个,那么后面新来的缓存直接丢弃;

复用:item复用时分为三级缓存,优先从mCacheViews中去寻找,找不到则去找自定义的缓存策略,如果没有定义则去缓存池中进行查找,如果都没有找到可以复用的item 则调用Adapter的createViewHolder,如果任何缓存中找到可以复用的item,则调用Adapter的bindViewHolder方法;

你可能感兴趣的:(Android,进阶学习)