当前位置:网站首页>真的很难理解?RecyclerView 缓存机制到底是几级缓存?
真的很难理解?RecyclerView 缓存机制到底是几级缓存?
2022-07-27 20:46:00 【码中之牛】
0.前言

RecyclerView 的缓存机制,可谓是面试中的常客了。不仅如此,在使用过程中,如果了解这个缓存机制,那么可以更好地利用其特性做开发。
那么,我们将以场景化的方式,讲解 RecyclerView 的缓存机制。常见的两个场景是:
1.滑动 RecyclerView 下的缓存机制
2.RecyclerView 初次加载过程的缓存机制
本文将讲解 滑动 RecyclerView 下 的缓存机制
1.缓存层级
背景知识:负责回收和复用 ViewHolder 的类是 Recycler,负责缓存的主要就是这个类的几个成员变量。我们贴点源码看看(下面源码的注释(和我写的注释),很重要,要记得认真看哦)
/** * A Recycler is responsible for managing scrapped or detached item views for reuse. * A "scrapped" view is a view that is still attached to its parent RecyclerView but that has been marked for removal or reuse. * * Typical use of a Recycler by a RecyclerView.LayoutManager will be to obtain views * for an adapter's data set representing the data at a given position or item ID. * If the view to be reused is considered "dirty" the adapter will be asked to rebind it. * If not, the view can be quickly reused by the LayoutManager with no further work. * Clean views that have not requested layout may be repositioned by a LayoutManager without remeasurement. */
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();// 存放可见范围内的 ViewHolder (但是在 onLayoutChildren 的时候,会将所有 View 都会缓存到这), 从这里复用的 ViewHolder 如果 position 或者 id 对应的上,则不需要重新绑定数据。
ArrayList<ViewHolder> mChangedScrap = null;// 存放可见范围内并且数据发生了变化的 ViewHolder,从这里复用的 ViewHolder 需要重新绑定数据。
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); // 存放 remove 掉的 ViewHolder,从这里复用的 ViewHolder 如果 position 或者 id 对应的上,则不需要重新绑定数据。
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; // 默认值是 2
int mViewCacheMax = DEFAULT_CACHE_SIZE; // 默认值是 2
RecycledViewPool mRecyclerPool; // 存放 remove 掉,并且重置了数据的 ViewHolder,从这里复用的 ViewHolder 需要重新绑定数据。 // 默认值大小是 5
private ViewCacheExtension mViewCacheExtension; // 自定义的缓存
}
至于到底有几级缓存,我觉得这个问题不大重要。有人说三层,有人说四层。有人说三层,因为觉得自定义那层,不是 RecyclerView 实现的,所以不算;也有人认为 Scrap 并不是真正的缓存,所以不算。
从源码看来,我更同意后者,Scrap 不算一层缓存。因为在源码中,mCachedViews 被称为 first-level。至于为什么 Scrap 不算一层,我的理解是:因为这层的只是 detach 了,并没有 remove,所以这层也没有缓存大小的概念,只要符合规则就会加入进去。
// Search the first-level cache
final int cacheSize = mCachedViews.size();

2.场景分析:滑动中的 RecyclerView 缓存机制

通过 Android Studio 的 Profiles 工具,我们可以看到调用流程

入口是 ouTouchEvent
通过表格的方式,简要说明上图的流程都在做什么?

通过上述表格,我们知道了。最重要的东西那就是 scrollBy 中调用了 fill 的方法了。那我们看看 fill 在做什么吧?滑出去的 View 最后去哪里了呢?滑进来的 View 是怎么来的?(带着这个问题,我们一起来读源码!一定要带着),源码只留下了核心部分
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;
}
}
}
//分析2
void 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;
}
3.总结
做一个总结,在分析源码前,我们提出了三个问题,那看看答案是什么吧
Q:那我们看看 fill 在做什么吧?
A:其实就是分析1(回收 ViewHolder ) + 分析 2 ( 复用 ViewHolder )
Q:滑出去的 View 最后去哪里了呢?
A:先尝试回收到 mCachedViews 中,未成功,则回收到 RecycledViewPool 中。
Q:滑进来的 View 是怎么来的?
A:如果是 isPreLayout 则先从 mChangedScrap 中尝试获取。
未获取到,再从 mAttachedScrap / mHiddenViews / mCachedViews (通过 position ) 中尝试获取
未获取到,再从 mAttachedScrap / mCachedViews (通过 id)中尝试获取
未获取到,再从 自定义缓存中尝试获取
未获取到,再从 RecycledViewPool 中尝试获取
未获取到,创建一个新的 ViewHolder
推荐阅读:
边栏推荐
- Blender plug-in of 2022
- Blood spitting finishing nanny level series tutorial - playing Fiddler bag capturing tutorial (5) - detailed explanation of fiddler monitoring panel
- 我年薪100万,全身上下没有超过100块的衣服:存钱,是最顶级的自律
- [signal denoising] signal denoising based on Kalman filter with matlab code
- XML 外部实体 (XXE) 漏洞及其修复方法
- LANproxy mapping local development environment
- 使用灰度滤镜
- 【CVA估值训练营】如何快速读懂上市公司年报——第四讲
- Xu Jinbo: AI protein prediction and design
- 毕设-基于SSM高校后勤管理系统
猜你喜欢

Library management system based on SSM framework

Implicit indicators for evaluating the advantages and disadvantages of automated testing

简单实用的数据可视化案例

iMeta | 国际标准刊号ISSN印刷版正式确认,双ISSN申请完成

Deploy dolphin scheduler high availability cluster based on rainbow

Application of user portrait in precise push of wechat official account of scientific journals

机器学习项目可视化展示方法

Huawei Hongmeng 3 was officially released, and this security feature has solved a major pain point
软件测试功能测试全套常见面试题【功能测试】面试总结4-2

【ELM分类】基于核极限学习机和极限学习机实现UCI数据集分类附matlab代码
随机推荐
技术认证 | 图扑软件携手华为云再创合作共赢新局面
初步了解Panda3D音频和高级交互组件
Exercise --- BFS
Winform怎么使用FTP实现自动更新
携手长江存储,江波龙推出全球最小扩展卡
See how Gan controls the image generation style step by step? Explain the evolution process of stylegan in detail
[signal denoising] signal denoising based on Kalman filter with matlab code
日产1500万只!比亚迪口罩拿下美国加州10亿美元订单
怎么使用xshell免费版
The print version of imeta | international standard ISSN is officially confirmed, and the application for dual ISSN is completed
Pro multi store version system, versatile is it!
深入了解 XXE 注射
Visual display method of machine learning project
MapReduce(三)
你的列表很卡?这4个优化能让你的列表丝般顺滑
WWW 2019 | HAN:异质图注意力网络
Learn more about xxE injection
NDK series (6): let's talk about the way and time to register JNI functions
三次握手的Socket交互流程
The security dilemma of software supply chain faced by enterprises