在上篇中我们简单分析了下listview的layout过程和回收机制,那这里我们将对RecyclerView的回收机制进行一波带走,please ready!
在ListView的复用中,主要是以View为单位的,而在RecyclerView中主要是以ViewHolder为单位,其实这一点我们从各自的Adapter中就可以看出:
ListView:主要重写createViewFromResource()和getView()方法。
RecyclerView:主要重写onCreateViewHolder()和onBindViewHolder()方法。而且RecyclerView已经帮我们实现了view的复用。
注:这里的ViewHolder也不像ListView中一样仅仅保存一个view,RecyclerView中的ViewHolder不仅仅包含view,还包括view的当前position、以前的position、itemId、itemViewType、当前的状态信息等等,这些丰富的信息可以把每一个view的状态区分开来,为我们RecyclerView实现局部刷新的功能打下了基础。
Recycler
我们知道,RecyclerView的回收复用主要靠Recycler来实现。首先,RecyclerView直接继承自ViewGroup,而Recycler则是RecyclerView中的final类,那么来看看这个类:
官方解释:Recycler是用来管理废弃view的复用。
可以看到Recycler的属性中有mAttachedScrap、mChangedScrap、mCachedViews、mRecyclerPool、mViewCacheExtension这几个管理View的集合,这也对应了Recycler中的四级缓存:
scrap view:mAttachedScrap、mChangedScrap。官方对scrapview的解释是:已标记为要删除或重复使用但仍然与其父RecyclerView绑定的视图。在这一级,mAttachedScrap是用来缓存屏幕上的viewholder(主要在recyclerview的layout过程中的进行),而mChangedScrap则回收屏幕上数据有变化的viewholder。
mCachedViews:缓存屏幕外的ViewHolder,默认为2个。
mViewCacheExtension:需要用户定制,默认不实现。ViewCacheExtension是一个抽象类,帮助开发者缓存view、自定义缓存控制策略。
mRecyclerPool:缓存池,多个RecyclerView共用,但存在这里的 ViewHolder 的数据信息会被重置掉,所以需要重新调用 onBindViewHolder 来绑定数据。每种type的pool容量为5。每种type的底层是一个ArrayList,然后所有的ArrayList又放在map(sparseArray)中
复用
Recycler类的入口(被调用最多的)是 getViewForPosition()方法,也是复用的核心入口。方法返回我们想要的view(复用的or重新创建的)。
最终调用的还是tryGetViewHolderForPositionByDeadline()方法,下面的分析主要都是这个方法里的逻辑:
可以看到首先会去mChangedScrap寻找我们想要的view,如果没有再去调用getScrapOrHiddenOrCachedHolderForPosition()方法去scarpview中寻找,可以看出,先遍历mAttachedScrap集合寻找对应的viewholder。其实在上面已经提到了mAttachedScrap主要是在recyclerview重新layout的时候回收当前在屏幕上显示的viewholder,便于下次attach,这一点是和listview的activeViews类似的,它们的生命周期都是layout过程。
|
|
继续看下一步,这dryRun值是从getViewForPosition这里传入的false,官方解释为如果为true代表viewholder不能从scrap或cache中移除。emmm…..不懂,先跳过,反正这里为false,继续往下。进入if,去mHiddenViews寻找合适的view,这里的mHiddenViews主要是存储当前隐藏起来的view,其实我们平时常用的滑动复用用不到这一块,所以跳过继续向下走。接下来就会去mCachedViews中寻找,如果没有则返回null。
其实这一块已经将从scrap或cache中的复用走了一遍,那我们回到tryGetViewHolderForPositionByDeadline()继续向下走
如果找到viewholder的话,判断viewholder是否符合条件,比如是否已经被remove、type and id是否匹配,如果不符合条件返回null。
继续向下找,这一块有两部分,第一部分还是从scrap和cache view中寻找,只不过是根据id来找,逻辑和上面分析的通过position查找类似;第二部分是从mViewCacheExtension中寻找,这部分缓存是由我们自定义的,默认为null,所以跳过。继续向下走
下一步就到了pool,这一步就是从mRecyclerPool中根据type寻找viewholder,可以看到首先判断pool中是否缓存有该type的viewholder,若无返回null,若有则返回pool中最后一个viewholder缓存。拿出来就可以直接用,但是这里通过resetInternal()重置了数据,变成一张白纸,需要等待重新绑定数据。
继续向下走,如果holder还为null,则调用mAdapter.createViewHolder(),重新创建新的viewholder。并且在这一步会去mRecyclerPool创建对应type的ScrapData。
tryGetViewHolderForPositionByDeadline()的这点尾巴主要是对viewholder重新绑定数据、检查和设置布局参数。可以看到在rebind的判定条件里,像holder.isBound()这样的,从源码中可以看出(这里就不截取了),都是对mFlags进行不为0的判断,但是像上面提到的重置viewholder后,就会将mFlags设为0,所以就进入if语句中调用tryBindViewHolderByDeadline()方法,从而继续去调用mAdapter.bindViewHolder()。最后返回一个我们需要的holder。
到这里,recycler的复用机制已经大概明了了,可以大致理解成下图:
图中的每一步都是需要判断条件的,所以在不同的场景下可能会有不同的“解法”,每一步都有可能输出对应的viewholder,而且在上述的分析过程也省略了一些情况,像notifyDataSetChanged()或者重新setAdapter这些场景,都会触发不同的缓存策略,这里只是笼统的介绍了大致流程,遇到具体需求还得具体分析。
回收
上面我们简单分析了下怎么拿,下面我们看看怎么放,因为回收的入口比较多,所以我们就挑个常见的滑动场景进行分析。
滑动recyclerview的时候,由layoutmanager的removeAndRecycleView()进行回收,最后会调用Recycler的recycleView()方法
首先判断view是否是attached状态,如果是,则需要remove并且进行detached,我们在上面分析过,attached的view会存放在mAttachedScrap缓存中,这里面的view是不需要rebind的,所以这里对view解绑并且从scrap中移除。后面主要就是recycleViewHolderInternal()的逻辑。
recycleViewHolderInternal()里面的逻辑主要是对mCachedViews和pool的操作。这里会保证mCachedViews不超过默认大小2(mViewCacheMax),一旦大于或等与这个值,就会将mcachedViews中的0号位置的viewholder放入pool中,如果不存在这种type的pool,则会去创建新的scrapData,所以一般来说在滑动recyclerview的过程中,一开始除了会创建所能看见的一屏的item数量的viewholder,后面如果继续滑动还会创建3个新的包括cacheviews中的2个和pool中的1个,之后的滑动才会对这些viewholder进行复用(这里假定一行只有一个item)。
其实,mcachedViews中存储的一直都是要回收的holder,而且因为mcachedViews中holder复用的条件较高,需要是原来位置的item才可以获取到,所以pool中的holder才是我们要复用调用的holder。
小结
到这里,recyclerview的回收复用机制就基本过了一遍,笔记有点长,大家可以分开来看。其实recycler有些地方与listview比较像,像layout过程中scrapview的思想,但是也可以看出来RecyclerView更加注重view的回收复用,它将view的layout等操作都交给了layoutmanager,这些等后面有时间了再分析一波。