写点什么

深入 RecyclerView 学习—缓存机制,kotlin 带参数的单例模式

用户头像
Android架构
关注
发布于: 2021 年 11 月 06 日

private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;


int mViewCacheMax = DEFAULT_CACHE_SIZE;


// 缓存池


RecycledViewPool mRecyclerPool;


// 自定义缓存


private ViewCacheExtension mViewCacheExtension;


static final int DEFAULT_CACHE_SIZE = 2; // 对应 mCachedViews 默认大小


}


上面的代码也很容易看到 RecyclerView 缓存的位置,这里大概可以分为 4 种类别:


  • Scrap 缓存:对应当前已加载的视图,也就是屏幕中的视图;

  • Cache 缓存:刚刚移出屏幕的 Item,默认大小是 2 个。缓存中的 Item 匹配后可以直接展示。根据 FIFO 原则先把老的数据放入下一级缓存中(比如:RecycledViewPool),然后添加新数据;

  • ViewCacheExtension 缓存:用来给开发者自定义的缓存。通过 type 和 position 来查找缓存;

  • RecycledViewPool 缓存:默认的缓存数量是 5 个,优先级没有 Cache 缓存高,同时缓存中的 Item 需要再次调用 onBindViewHolder()方法;

[](

)缓存原理分析


首先看源码是现有大致的思路,根据上面讲到的缓存流程来看,在 RecyclerView 滑动时会查找缓存中是否有匹配的数据。所以缓存机制的工作是从滑动开始的,那么滑动就会触发 View 的工作流程(measure、layout、draw)根据接下来这个思路去看代码。


具体特别细节的源码就不再一一叙述,看源码也不是为了记住所有的细节,而是为了学习主要的逻辑跟设计的模式。


RecyclerView 的测量、绘制都是委托给 LayoutManager 的,在 LinearLayoutManager 中,可以找到相关的代码。


@Override


public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {


//...


detachAndScrapAttachedViews(recycler);


//...


}


在 LinearLayoutManager 的 onLayoutChildren()方法中只截取了这一行代码,它的作用就是处理 Scrap 缓存以及 detach 的 ViewHolder。


/**


  • Temporarily detach and scrap all currently attached child views. Views will be scrapped

  • into the given Recycler. The Recycler may prefer to reuse scrap views before

  • other views that were previously recycled.

  • @param recycler Recycler to scrap views into


*/


public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {


final int childCount = getChildCount();


for (int i = childCount - 1; i >= 0; i--) {


final View v = getCh


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


ildAt(i);


scrapOrRecycleView(recycler, i, v);


}


}


从代码的注释中写的很清楚处理视图的缓存逻辑;接下来继续分析。


private void scrapOrRecycleView(Recycler recycler, int index, View view) {


final ViewHolder viewHolder = getChildViewHolderInt(view);


if (viewHolder.shouldIgnore()) {


if (DEBUG) {


Log.d(TAG, "ignoring view " + viewHolder);


}


return;


}


if (viewHolder.isInvalid() && !viewHolder.isRemoved()


&& !mRecyclerView.mAdapter.hasStableIds()) {


// ViewHolder 是 indalid 的


removeViewAt(index);


recycler.recycleViewHolderInternal(viewHolder);


} else {


// ViewHolder 对应的 View 是 attach 的


detachViewAt(index);


recycler.scrapView(view);


mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);


}


}


在 scrapOrRecycleView()方法中有两个逻辑分支,分别处理在屏幕中的 Item 以及需要回收的 Item。我们先看如何处理屏幕中的 Item。


void scrapView(View view) {


final ViewHolder holder = getChildViewHolderInt(view);


if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)


|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {


if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {


holder.setScrapContainer(this, false);


mAttachedScrap.add(holder);


} else {


if (mChangedScrap == null) {


mChangedScrap = new ArrayList<ViewHolder>();


}


holder.setScrapContainer(this, true);


mChangedScrap.add(holder);


}


}


这里主要处理的就是将 ViewHolder 加入 Scrap 缓存中。接下来分析另一种情况。


void recycleViewHolderInternal(ViewHolder holder) {


//...


if (forceRecycle || holder.isRecyclable()) {


if (mViewCacheMax > 0


&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID


| ViewHolder.FLAG_REMOVED


| ViewHolder.FLAG_UPDATE


| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {


// Retire oldest cached view


int cachedViewSize = mCachedViews.size();


if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {


recycleCachedViewAt(0);


cachedViewSize--;


}


int targetCacheIndex = cachedViewSize;


//...


mCachedViews.add(targetCacheIndex, holder);


cached = true;


}


if (!cached) {


addViewHolderToRecycledViewPool(holder, true);


recycled = true;


}


}


//...


}


recycleViewHolderInternal()方法中主要是对 Cache 缓存和 RecycledViewPool 缓存进行处理。判断依据主要是根据 ViewHolde 的标志位进行判断。


  • 对于 Cache 缓存:缓存池满了以后就移出第 1 个然后加入新的缓存数据。

  • 对于 RecycledViewPool 缓存:主要根据 ViewHolder 的标志位进行判断(比如 VIewHolder 有 FLAG_REMOVED 标志)就会加入到 RecycledViewPool 缓存。


在上面的代码中主要分析了 RecyclerView 中缓存数据的来源,接下来继续分析如何使用缓存。按照之前的思路,在 LayoutManager 执行 onLayoutChildren()方法时会获取新的 Item 这个时候就会触发 RecyclerView 的缓存机制。


@Override


public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {


//...


fill(recycler, mLayoutState, state, false);


//...


}


int fill(RecyclerView.Recycler recycler, LayoutState layoutState,


RecyclerView.State state, boolean stopOnFocusable) {


//...


layoutChunk(recycler, state, layoutState, layoutChunkResult);


//...


}


void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,


LayoutState layoutState, LayoutChunkResult result) {


//...


View view = layoutState.next(recycler);


//...


}


View next(RecyclerView.Recycler recycler) {


if (mScrapList != null) {


return nextViewFromScrapList();


}


final View view = recycler.getViewForPosition(mCurrentPosition);


mCurrentPosition += mItemDirection;


return view;


}


这次就不啰嗦了,直接把前面需要执行的流程都列了出来,可以看到 LayoutManager 是通过 fill()方法来填充布局的,在这个过程中最终会通过 Recycler 的 getViewForPosition()方法获取到需要添加的 View。接下来就从 Recycler 中进行分析。


在 Recycler 中获取 View 的方法最终是在 tryGetViewHolderForPositionByDeadline()方法执行的:


ViewHolder tryGetViewHolderForPositionByDeadline(int position,


boolean dryRun, long deadlineNs) {

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
深入RecyclerView学习—缓存机制,kotlin带参数的单例模式