recyclerview缓存机制(到底是几级缓存)
RecyclerView 的缓存机制,可谓是面试中的常客了。不仅如此,在使用过程中,如果了解这个缓存机制,那么可以更好地利用其特性做开发。
那么,我们将以场景化的方式,讲解 RecyclerView 的缓存机制。常见的两个场景是:
- 滑动 RecyclerView 下的缓存机制
- 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 缓存机制
通过 Android Studio 的 Profiles 工具,我们可以看到调用流程
入口是 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页)等等,这些资料整理给大家,希望踩过的坑不要再踩,遭遇的技术瓶颈一次性消灭。
如果需要的话,可以顺手帮我点赞评论一下,直接私信我【笔记】免费领取!
部分内容展示如下
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文件操作
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渲染机制
03.Android常用组件:
- Activty:Activity管理栈与Activity的启动模式、Activity生命周期源码分析
- Fragment:Fragment生命周期深入详解、Fragment事务管理机制详解、性能优化相关方案
- Service:Service启动模式分析、Service管理与通信方案、Service生命周期底层详解
04.高级UI:
- UI绘制原理:setContentView()方法下到底做了什么、AppCompatActivity与Activity的区别、UI测量、布局、绘制的底层执行流程
- 插件换肤:LayoutInflater加载布局分析、Android资源的加载机制、Resource与AssetManager
- 事件分发机制原理:事件执行U形链与L形链、事件拦截原理
- 属性动画:VSYNC刷新机制、ObjectAnimator与ValueAnimator源码讲解、Android属性动画:插值器与估值器
- RecycleView:布局管理器LayoutManager详解、回收池设计思想、适配器模式原理
- 高阶贝塞尔曲线
05.Jetpack:
- Lifecycle:Lifecycle源码、Lifecycle高阶应用
- ViewModel:ViewModel源码、ViewModel应用技巧
- LiveData:LiveData源码
- Navigation:Navigation源码
- Room:Room源码、Room+LiveData监听数据库数据变更刷新页面原理
- WorkManager内核
- Pagging原理
- DataBinding:单向绑定、双向绑定、如何与RecyclerView的配合使用、底层原理
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检查工具实践
如果需要的话,可以顺手帮我点赞评论一下,直接私信我【笔记】免费领取!
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音视频通话实现
08.开源框架原理:
- Okhttp
- Retrofit
- RxJava
- Glide
- Hilt
- Dagger2
- EventBus
- 组件化、插件化、热修复等
09.Gradle:
- Groovy语法
- Gradle Android插件配置
- Gradle实践等
10.kotlin:
- Kotlin语法
- 扩展使用
- 进阶使用
- 实践等
11.Flutter:
- Dart语法
- UI
- 进阶使用
- 优化
- 实践等
12.鸿蒙:
- Ability组件
- 分布式任务
- 事件总线
- 鸿蒙线程
- UI自定义控件等
本文仅代表作者观点,不代表本站支持或者同意该观点。其原创性、真实性未经本站证实,其内容仅供参考,本站不对其内容承担任何责任。