当前位置:首页 > 生活百科 > recyclerview缓存机制(到底是几级缓存)

recyclerview缓存机制(到底是几级缓存)

娇娇3个月前 (03-02)生活百科36

RecyclerView 的缓存机制,可谓是面试中的常客了。不仅如此,在使用过程中,如果了解这个缓存机制,那么可以更好地利用其特性做开发。

那么,我们将以场景化的方式,讲解 RecyclerView 的缓存机制。常见的两个场景是:

  1. 滑动 RecyclerView 下的缓存机制
  2. RecyclerView 初次加载过程的缓存机制

本文将讲解 滑动 RecyclerView 下 的缓存机制

一、缓存层级

背景知识:负责回收和复用 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 cachefinal int cacheSize = mCachedViews.size();

类型

变量名

存储说明

备注

Scrap

mAttachedScrap

存放可见范围内的 ViewHolder

从这里复用的 ViewHolder 如果 position 或者 id 对应的上,则不需要重新绑定数据。在 onLayoutChildren 的时候,会将所有 View 都会缓存到这

mChangedScrap

存放可见范围内并且数据发生了变化的 ViewHolder

从这里复用的 ViewHolder 需要重新绑定数据。

Cache

mCachedViews

存放 remove 掉的 ViewHolder

从这里复用的 ViewHolder 如果 position 或者 id 对应的上,则不需要重新绑定数据。

ViewCacheExtension

mViewCacheExtension

自定义缓存

RecycledViewPool

mRecyclerPool

存放 remove 掉,并且重置了数据的 ViewHolder

从这里复用的 ViewHolder 需要重新绑定数据。

二、场景分析:滑动中的 RecyclerView 缓存机制

recyclerview缓存机制(到底是几级缓存)(1)

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

recyclerview缓存机制(到底是几级缓存)(2)

入口是 ouTouchEvent

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

方法名

隶属的类

作用描述

onTouchEvent()

RecyclerView

处理点击事件,在 MOVE 事件中在一定条件下,拦截事件后,做事件处理

scrollByInternal()

RecyclerView

主要是调用 scrollStep()

scrollStep()

RecyclerView

通过 dx 和 dy 的值判断是调用scrollHorizontallyBy()还是 scrollVerticallyBy()

scrollHorizontallyBy()/scrollVerticallyBy()

LayoutManager

主要是调用 scrollBy()

scrollBy()

LayoutManager

通过调用 fill() 添加滑进来的View 和回收滑出去的 View

offsetChildrenVertical()/offsetChildrenHorizontal()

RecyclerView

做偏移操作

通过上述表格,我们知道了。最重要的东西那就是 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;         }      }}
//分析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;}复制代码

总结

做一个总结,在分析源码前,我们提出了三个问题,那看看答案是什么吧

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

最后

这里也分享一些珍藏资源,从面试简历模板到大厂面经汇总,从大厂内部技术资料到互联网高薪必读书单,以及Android面试核心知识点(844页)和Android面试题合集2022年最新版(354页)等等,这些资料整理给大家,希望踩过的坑不要再踩,遭遇的技术瓶颈一次性消灭。

如果需要的话,可以顺手帮我点赞评论一下,直接私信我【笔记】免费领取!

部分内容展示如下

recyclerview缓存机制(到底是几级缓存)(3)

recyclerview缓存机制(到底是几级缓存)(4)

01.Android必备底层技术

  • Java序列化:Serializable原理、Parcelable接口原理、Json、XML
  • 注解、泛型与反射:自定义注解、注解的使用、泛型擦除机制、泛型边界、Java方法与Arm指令、Method反射源码、invoke方法执行原理
  • 虚拟机:JVM垃圾回收器机制、JVM内存分配策略、Android虚拟机与JVM底层区别、虚拟机底层Odex本地指令缓存机制、虚拟机如何分别加载class与object、虚拟机类加载模型
  • 并发:Java线程本质讲解、线程原理、线程通信、UnSafe类、线程池
  • 编译时技术:OOP面向切面之AspectJ、字节码手术刀JavaSSit实战、字节码插桩技术(ASM)实战
  • 动态代理:动态代理实现原理、动态代理在虚拟机中运行时动态拼接Class字节码分析、ProxyGenerator生成字节码流程
  • 高级数据结构与算法:HashMap源码、ArrayList源码、排序算法
  • Java IO:Java IO体系、IO文件操作

recyclerview缓存机制(到底是几级缓存)(5)

02.Framework

  • Binder:Linux内存基础、Binder四层源码分析、Binder机制、Binder进程通信原理
  • Handler:Loop消息泵机制、Message解析
  • Zygote:init进程与Zygote进程、Zygote启动流程、Socket通信模式、APP启动过程
  • AMS:ActivityThread源码分析、AMS与ActivityThread通信原理、Activity启动机制
  • PMS:PMS源码、APK安装过程分析、PMS对安装包的解析原理
  • WMS:PhoneWindow实例化流程、DecorView创建过程、ViewRootImpl渲染机制

recyclerview缓存机制(到底是几级缓存)(6)

03.Android常用组件

  • Activty:Activity管理栈与Activity的启动模式、Activity生命周期源码分析
  • Fragment:Fragment生命周期深入详解、Fragment事务管理机制详解、性能优化相关方案
  • Service:Service启动模式分析、Service管理与通信方案、Service生命周期底层详解

recyclerview缓存机制(到底是几级缓存)(7)

04.高级UI

  • UI绘制原理:setContentView()方法下到底做了什么、AppCompatActivity与Activity的区别、UI测量、布局、绘制的底层执行流程
  • 插件换肤:LayoutInflater加载布局分析、Android资源的加载机制、Resource与AssetManager
  • 事件分发机制原理:事件执行U形链与L形链、事件拦截原理
  • 属性动画:VSYNC刷新机制、ObjectAnimator与ValueAnimator源码讲解、Android属性动画:插值器与估值器
  • RecycleView:布局管理器LayoutManager详解、回收池设计思想、适配器模式原理
  • 高阶贝塞尔曲线

recyclerview缓存机制(到底是几级缓存)(8)

05.Jetpack

  • Lifecycle:Lifecycle源码、Lifecycle高阶应用
  • ViewModel:ViewModel源码、ViewModel应用技巧
  • LiveData:LiveData源码
  • Navigation:Navigation源码
  • Room:Room源码、Room+LiveData监听数据库数据变更刷新页面原理
  • WorkManager内核
  • Pagging原理
  • DataBinding:单向绑定、双向绑定、如何与RecyclerView的配合使用、底层原理

recyclerview缓存机制(到底是几级缓存)(9)

06.性能优化

  • 启动优化:系统启动原理、Trace工具分析启动卡顿、类重排机制、资源文件重排机制
  • 内存优化
  • UI渲染优化:UI层级规范及对UI加载的影响、UI卡顿原因及修复、UI绘制、布局、测量原因以及处理方案
  • 卡顿优化:造成卡顿的原因分析、内存抖动与GC回收、回收算法
  • 耗电优化
  • 崩溃优化:项目崩溃异常捕获、优雅的异常处理方案、如何避免异常弹框
  • 安全优化:APP加固实现(防反编译,dex加固)、https防抓包机制(数据传输加载,客户端服务器端双向加密校验)
  • 网络优化:serializable原理、parcelable接口原理、http与https原理详解、protbuffer网络IO详解、gzip压缩方案
  • 大图加载优化:Glide巨图加载机制原理分析、大图多级缓存实现方案
  • 多线程并发优化
  • 储存优化:Android文件系统-sdcard与内存存储、Shared Preference原理、MMAP内存映射
  • 安装包优化:shrinkResources去除无用资源、合理设置多语言、webp实现图片瘦身、合理配置armable-v7的so库、Lint检查工具实践

recyclerview缓存机制(到底是几级缓存)(10)

如果需要的话,可以顺手帮我点赞评论一下,直接私信我【笔记】免费领取!

07.音视频

  • C/C++:数据类型、数组、内存布局、指针、函数、预处理器、结构体、共用体、容器、类型转换、异常、文件流操作、线程
  • H.265/H.265:音视频格式封装原理、编码原理、视频流H264的组装原理切片NAL单元、视频流H264码流分析、切片与宏快,运动矢量、信源编码器、高频滤波、帧间拆分与帧内预测、CTU,PU TU编码结构、DSP芯片解码流程、MediaPlayer与DSP芯片交互机制、投屏架构、MediaProjection与MeidiaCodec交互机制、H265码流交换
  • MediaCodec:dsp芯片、编解码器的生命周期、解码器中输入队列与解析队列设计思想、MediaCodec中平缓解码解析、MediaExtractor 多路复用、MediaMuxer合成器、MediaFormat格式
  • 音视频剪辑:视频剪辑、音频剪辑、音频合成、音谱显示、视频倒放
  • 音视频直播:硬编码、软编码、native实现rtmp推流、摄像头预览帧编码NV21转YUV、视频画面封装拼接Packet包、音频流数据拼接Packet包、RtmpDump实时同步发送音视频数据、MediaProjection、Medicodec编码H264码流、rtmp推流
  • OpenGL与音视频解码:OpenGL绘制流程、矩阵、Opencv详解、人脸识别效果实现
  • OpenGL特效:CPU与GPU运行机制详解、世界坐标,布局坐标,与FBO坐标系、图像镜像与旋转处理、人脸定位与关键点定位、大眼效果、贴纸效果、美颜效果
  • FFmpeg万能播放器:FFmpeg结构体、声音播放原理、Surface的渲染、像素绘制原理与对齐机制、音视频同步原理、视频播放器整体架构
  • Webrtc音视频通话:WebRtc服务端环境搭建与Webrtc编译、1v1视频通话实现方案、群聊视频通话实现思路、多对多视频会议实现、1V1音视频通话实现

recyclerview缓存机制(到底是几级缓存)(11)

08.开源框架原理

  • Okhttp
  • Retrofit
  • RxJava
  • Glide
  • Hilt
  • Dagger2
  • EventBus
  • 组件化、插件化、热修复等

recyclerview缓存机制(到底是几级缓存)(12)

09.Gradle

  • Groovy语法
  • Gradle Android插件配置
  • Gradle实践等

recyclerview缓存机制(到底是几级缓存)(13)

10.kotlin

  • Kotlin语法
  • 扩展使用
  • 进阶使用
  • 实践等

recyclerview缓存机制(到底是几级缓存)(14)

11.Flutter

  • Dart语法
  • UI
  • 进阶使用
  • 优化
  • 实践等

recyclerview缓存机制(到底是几级缓存)(15)

12.鸿蒙

  • Ability组件
  • 分布式任务
  • 事件总线
  • 鸿蒙线程
  • UI自定义控件等

recyclerview缓存机制(到底是几级缓存)(16)

本文仅代表作者观点,不代表本站支持或者同意该观点。其原创性、真实性未经本站证实,其内容仅供参考,本站不对其内容承担任何责任。

本文链接:https://www.hloby.com/20230310450.html

相关文章

舍利子是什么东西(舍利子是什么东西科学解释)

舍利子是什么东西(舍利子是什么东西科学解释)

综述 公元819年,长安偌大的宫殿里面,41岁的唐宪宗坐在书案前,面色阴沉,眼睛里面在闪着火。他把朱笔放在左上,看着正放在眼前的奏折,拳头紧握,一时之间说不出话。 周边侍奉的宦官也低着...

民国是哪一年开始(如何详细的了解民国历史)

民国是哪一年开始(如何详细的了解民国历史)

在大陆,中华民国的存在周期是1912年至1949年,短短的30余年,政府更迭、各种势力轮番登场,波澜壮阔、热闹非凡、可谓乱世。 1912年1月1日,孙中山宣誓就职,亚洲第一个民主共和国——中...

办准生证需要什么证件(孩子出生一定要办这些证件)

办准生证需要什么证件(孩子出生一定要办这些证件)

1、准生证 孩子出生之前我们首先需要办理的就是准生证,准生证是计划生育服务证之一,是必须在孩子出生之前办理的,当然可以在孕前,也可以在孕后。 准生证 [送心]最好是在...

地中海气候特点及分布(地中海气候真实感受)

地中海气候特点及分布(地中海气候真实感受)

提到地中海气候,人们首先想到的是地中海沿岸地区。意大利、摩洛哥、利比亚、土耳其、西班牙和希腊等国家是许多人想到的地方。这是一个与气候同名的地区,以及气候本身。然而,这并不是唯一一个地中海气...

车钥匙丢了怎么办(解决方法都在这)

车钥匙丢了怎么办(解决方法都在这)

生活中难免有丢三落四的时候,小小的车钥匙不起眼,但是,有些车主朋友还是一不小心丢了自己的汽车钥匙,万一车钥匙丢了,车子开不了,到底应该怎么办呢? 今天轮谷就来给朋友们说说这个话题,希望对朋友...

泰山在哪个省哪个市(这篇文章告诉你)

泰山在哪个省哪个市(这篇文章告诉你)

泰山我国五岳之首,具有“天下第一山”的称号。泰山海拔1545米,总面积24200公顷。泰山以通天拔地之势雄峙东方,凌驾于齐鲁大地。泰山以其有容乃大的气魄,将地质地貌、自然景观、历史文化和谐地融为一...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。