RecyclerView的缓存机制是为了提高列表滚动时的性能。采用了多级缓存策略来存储和复用视图(View),减少视图的创建和销毁,进而减少内存分配和GC的频率。
负责回收和复用ViewHolder的类是Recycler,负责缓存的主要就是这个类的几个成员变量。
public final class Recycler { // 存放可见范围内的 ViewHolder (但是在 onLayoutChildren 的时候,会将所有 View 都会缓存到这), 从这里复用的 ViewHolder 如果 position 或者 id 对应的上,则不需要重新绑定数据。 final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); // 存放可见范围内并且数据发生了变化的 ViewHolder,从这里复用的 ViewHolder 需要重新绑定数据。 ArrayList<ViewHolder> mChangedScrap = null; // 存放 remove 掉的 ViewHolder,从这里复用的 ViewHolder 如果 position 或者 id 对应的上,则不需要重新绑定数据。 final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); // 默认值是 2 private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; // 默认值是 2 int mViewCacheMax = DEFAULT_CACHE_SIZE; // 存放 remove 掉,并且重置了数据的 ViewHolder,从这里复用的 ViewHolder 需要重新绑定数据。 // 默认值大小是 5 RecycledViewPool mRecyclerPool; // 自定义的缓存 private ViewCacheExtension mViewCacheExtension; }
RecyclerView的缓存机制主要由四个部分组成,它们按照从高到低的优先级排列:
包括mAttachedScrap和mChangedScrap,也称为屏内缓存,因为它们主要用于保存屏幕内当前可见或者即将可见的ViewHolder。
mAttachedScrap:存放的是已添加到RecyclerView但与RecyclerView临时分离(例如在滚动或布局调整过程中)的ViewHolder。
mChangedScrap:存放的是数据已改变但尚未重新绑定数据的ViewHolder,通常用于动画播放等场景。
又称离屏缓存,用于保存最新被移除(remove)但尚未被回收的ViewHolder。
缓存的大小是有限制的,默认最大数量为2(由DEFAULT_CACHE_SIZE定义)。
当需要展示新视图时,会首先检查Cache缓存中是否有可用的ViewHolder。
「ViewCacheExtension」
为开发者预留的缓存池,允许开发者自定义缓存策略,存储更多的或特定类型的ViewHolder。
开发者可以通过实现ViewCacheExtension接口来扩展缓存功能。
「RecycledViewPool(mRecyclerPool)」
终极的回收缓存池,用于存放被标识为废弃(即其他缓存池不再需要的)的ViewHolder。
这些ViewHolder已经被抹除了数据,需要重新绑定数据才能使用。
RecycledViewPool会根据不同的item类型创建不同的List来存储ViewHolder。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { // max offset we should set is mFastScroll + available final int start = layoutState.mAvailable; //首选该语句块的判断,判断当前状态是否为滚动状态,如果是的话,则触发 recycleByLayoutState 方法 if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { // TODO ugly bug fix. should not happen if (layoutState.mAvailable < 0) { layoutState.mScrollingOffset += layoutState.mAvailable; } // 分析1----回收 recycleByLayoutState(recycler, layoutState); } while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { //分析2----复用 layoutChunk(recycler, state, layoutState, layoutChunkResult); }}// 分析1----回收 // 通过一步步追踪,我们发现最后调用的是 removeAndRecycleViewAt() public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) { final View view = getChildAt(index); //分析1-1 removeViewAt(index); //分析1-2 recycler.recycleView(view);}// 分析1-1// 从 RecyclerView 移除一个 View public void removeViewAt(int index) { final View child = getChildAt(index); if (child != null) { mChildHelper.removeViewAt(index); }}//分析1-2 // recycler.recycleView(view) 最终调用的是 recycleViewHolderInternal(holder) 进行回收 VH (ViewHolder)void recycleViewHolderInternal(ViewHolder holder) { if (forceRecycle || holder.isRecyclable()) { //判断是否满足放进 mCachedViews if (mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID| ViewHolder.FLAG_REMOVED| ViewHolder.FLAG_UPDATE| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)){ // 判断 mCachedViews 是否已满 if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { // 如果满了就将下标为0(即最早加入的)移除,同时将其加入到 RecyclerPool 中 recycleCachedViewAt(0); cachedViewSize--; } mCachedViews.add(targetCacheIndex, holder); cached = true; } //如果没有满足上面的条件,则直接存进 RecyclerPool 中 if (!cached) { addViewHolderToRecycledViewPool(holder, true); recycled = true; } }}//分析2void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { //分析2-1 View view = layoutState.next(recycler); if (layoutState.mScrapList == null) { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { //添加到 RecyclerView 上 addView(view); } else { addView(view, 0); } }}//分析2-1//layoutState.next(recycler) 最后调用的是 tryGetViewHolderForPositionByDeadline() 这个方法正是 复用 核心的方法ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) { // 0) If there is a changed scrap, try to find from there // 例如:我们调用 notifyItemChanged 方法时 if (mState.isPreLayout()) { // 如果是 changed 的 ViewHolder 那么就先从 mChangedScrap 中找 holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder != null; } // 1) Find by position from scrap/hidden list/cache if (holder == null) { //如果在上面没有找到(holder == null),那就尝试从通过 pos 在 mAttachedScrap/ mHiddenViews / mCachedViews 中获取 holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); } if (holder == null) { // 2) Find from scrap/cache via stable ids, if exists if (mAdapter.hasStableIds()) { //如果在上面没有找到(holder == null),那就尝试从通过 id 在 mAttachedScrap/ mCachedViews 中获取 holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), } if (holder == null && mViewCacheExtension != null) { //这里是通过自定义缓存中获取,忽略 } //如果在上面都没有找到(holder == null),那就尝试在 RecycledViewPool 中获取 if (holder == null) { // fallback to pool holder = getRecycledViewPool().getRecycledView(type); if (holder != null) { //这里拿的是,要清空数据的 holder.resetInternal(); } } //如果在 Scrap / Hidden / Cache / RecycledViewPool 都没有找到,那就只能创建一个了。 if (holder == null) { holder = mAdapter.createViewHolder(RecyclerView.this, type); } } return holder;}
「注意」:当RecyclerView不再需要某个ViewHolder时(例如,当列表项被完全移出屏幕并且缓存已满时),ViewHolder会被放入RecycledViewPool并最终可能被系统回收。
本文链接:http://www.28at.com/showinfo-26-97296-0.htmlRecyclerView的缓存机制及使用策略
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 一个合理的前端应用文件结构
下一篇: 一个诡异的Json反序列化问题