【Android Framework系列】第12章 RecycleView相关原理及四级缓存策略分析

1 RecyclerView简介

RecyclerView是一款非常强大的widget,它可以帮助您灵活地显示列表数据。当我开始学习 RecyclerView的时候,我发现对于复杂的列表界面有很多资源可以参考,但是对于简单的列表展现就鲜有可参考的资源了。虽然RecyclerView的组成结构乍一看有些复杂,但是深入理解以后您会发现它其实非常简单明了。
RecyclerView一般作为Android显示列表的控件,有诸多优异的性能。回收池策略能加载上亿级数据并不发生卡顿,适配器模式能展示任意显示需求。
RecyclerView就像传送带,充分利用传送带原理,永远只有用户看到的数据才会加载到内存,而看不到的在等待被加载。传送带能够源源不断的传送亿级货物,RecyclerView也能够显示加载亿级Item

1.3 RecyclerView架构中核心组件

  1. 回收池:能回收任意Item控件,并返回符合类型的Item控件;
    比如 onBinderViewHodler方法中的第一个参数是从回收池中返回的
  2. 适配器:Adapter接口,经常辅助RecyclerView实现列表展示;适配器模式,将用户界面展示与交互分离
  3. RecyclerView:是做触摸事件的交互,主要实现边界值判断;根据用户的触摸反馈,协调回收池对象与适配器对象之间的工作

我们带着这几个问题来学习RecyclerView

  1. ListviewRecycerview的缓存差别
  2. RecyclerView滑出去的View到哪里去了
  3. RecyclerView如何复用回收池的View
  4. RecyclerView的四级缓存机制

1.4 RecyclerView滑动相关

众所周知,RecyclerViewandroid中实现列表是性能非常好的,那么性能好的原因在哪里呢?关键还是在它在处理view时的回收和复用。列表在滑动的时候,会进行itemView的回收和复用,那么我们就从滑动回调即onTouchEvent来入手分析

1.4.1 基本概念

  1. ViewHolder: View的容器,一项View就对应一个ViewHolder
  2. Recyler:RecyclerView的内部类,主要负责View的回收和复用
  3. LinearLayoutManager: RecyclerView的线性布局管理器

1.4.2 滑动时函数调用链

在这里插入图片描述
这里我们大概了解下滑动时的函数调用链,帮助理解后面分析四级缓存相关的思路。

1.4.3 onMeasrue初始化

RecyclerView的宽度和高度开发者们都喜欢设置层wrap_content或者match_parent。所以需要通过实际内容确定RecyclerView高度
情况1 : 当item数不足的时候,比如RecyclerView只加载了2个Item 以子控件总高度测算的高度为准
情况2 : 当item数量超过实际屏幕高度,以match_parent为准,也就是最大高度

1.4.4 onLayout

RecyclerView作为一个容器类控件 继承自ViewGroup。必须实现onLayout方法来子控件进行正确摆放,由于我们手写的RecyclerVIew是垂直的,摆放是由上至下进行。同时为了不将所有Item全部加载到内存 也需要进行准确的控制

1.4.5 事件拦截

RecyclerView作为一个容器类控件 需要拦截滑动事件,用户手指滑动则让所有子Item滑动,子Item在滑动中是接收不到任何事件的。当RecyclerVIew静止时,子Item需要接收到点击事件

2 RecyclerView适配器与回收池的工作机制

这里我们先以图片的形式了解一下RecyclerView相关的加载逻辑。

2.1 RecyclerView中的第一屏加载

在这里插入图片描述

2.2 RecyclerView中的第二屏

在这里插入图片描述

2.3 回收池回收策略

在这里插入图片描述

2.4 回收池填充策略

在这里插入图片描述

2.5 回收池设计

在这里插入图片描述

3 RecyclerView回收与复用

在这里插入图片描述

3.1 回收的关键方法分析

RecyclerView.java

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)) // 1) 先尝试放到cacheView中int cachedViewSize = mCachedViews.size();if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {// 如果 mCachedViews 已经满了,把第0个位置的移除并放到 缓存池中recycleCachedViewAt(0);cachedViewSize--;}if (!cached) {// 2) 如果CacheView中没放进去,就放到 缓存池中addViewHolderToRecycledViewPool(holder, true);recycled = true;}...
}

3.2 复用的关键方法分析

tryGetViewHolderForPositionByDeadline
从一级缓存 mChangeScrap 中取
从二级缓存 mCachedViews中取
从三级缓存 mViewCacheExtension 中取
从四级缓存 缓存池中取
缓存中都没有拿到值,就直接创建
未绑定过时,进行绑定

ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {ViewHolder holder = null;// 1) 从一级缓存 changed scrap 中取if (mState.isPreLayout()) {holder = getChangedScrapViewForPosition(position);fromScrapOrHiddenOrCache = holder != null;}// 2)从二级缓存 cache中取if (holder == null) {final int type = mAdapter.getItemViewType(offsetPosition);if (mAdapter.hasStableIds()) {holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);if (holder != null) {// update positionholder.mPosition = offsetPosition;fromScrapOrHiddenOrCache = true;}}// 3. 从三级缓存 CacheExtension 中取  if (holder == null && mViewCacheExtension != null) {final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);if (view != null) {holder = getChildViewHolder(view);}}// 4) 从四级缓存 缓存池中取if (holder == null) { // fallback to poolholder = getRecycledViewPool().getRecycledView(type);if (holder != null) {holder.resetInternal();if (FORCE_INVALIDATE_DISPLAY_LIST) {invalidateDisplayListInt(holder);}}}// 5)缓存中都没有拿到值,就直接创建if (holder == null) {holder = mAdapter.createViewHolder(RecyclerView.this, type);if (ALLOW_THREAD_GAP_WORK) {// only bother finding nested RV if prefetchingRecyclerView innerView = findNestedRecyclerView(holder.itemView);if (innerView != null) {holder.mNestedRecyclerView = new WeakReference<>(innerView);}}long end = getNanoTime();mRecyclerPool.factorInCreateTime(type, end - start);if (DEBUG) {Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");}}}// 6)已经 bind过了,不会再去绑定,未绑定过时,进行绑定if (mState.isPreLayout() && holder.isBound()) {holder.mPreLayoutPosition = position;} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {final int offsetPosition = mAdapterHelper.findPositionOffset(position);//  尝试 bindViewbound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);}return holder;
}

4 四级缓存机制

4.1 一级缓存-缓存碎片

ViewHolder getChangedScrapViewForPosition(int position) {// If pre-layout, check the changed scrap for an exact match.final int changedScrapSize;if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {return null;}// find by positionfor (int i = 0; i < changedScrapSize; i++) {final ViewHolder holder = mChangedScrap.get(i);if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);return holder;}}// find by idif (mAdapter.hasStableIds()) {final int offsetPosition = mAdapterHelper.findPositionOffset(position);if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {final long id = mAdapter.getItemId(offsetPosition);for (int i = 0; i < changedScrapSize; i++) {final ViewHolder holder = mChangedScrap.get(i);if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);return holder;}}}}return null;
}

4.2 二级缓存-缓存列表

ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {// 1) 先从mAttachedScrap中取,取到便返回final int count = mAttachedScrap.size();for (int i = count - 1; i >= 0; i--) {final ViewHolder holder = mAttachedScrap.get(i);if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {if (type == holder.getItemViewType()) {holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);if (holder.isRemoved()) {if (!mState.isPreLayout()) {holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE| ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);}}return holder;} else if (!dryRun) {mAttachedScrap.remove(i);removeDetachedView(holder.itemView, false);quickRecycleScrapView(holder.itemView);}}}// 2)二级缓存,从mCachedViews中取final int cacheSize = mCachedViews.size();for (int i = cacheSize - 1; i >= 0; i--) {final ViewHolder holder = mCachedViews.get(i);//从mCachedViews中取到后便返回if (holder.getItemId() == id) {if (type == holder.getItemViewType()) {if (!dryRun) {mCachedViews.remove(i);}return holder;} else if (!dryRun) {recycleCachedViewAt(i);return null;}}}return null;}

4.3 三级缓存-自定义缓存

这一级缓存为用户自定义,这里不做详解。

4.4 四级缓存-缓存池

public ViewHolder getRecycledView(int viewType) {//从mScrap中根据view的类型来取出一个final ScrapData scrapData = mScrap.get(viewType);if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;//从 scrapData 中拿最后一个数据,先进后出return scrapHeap.remove(scrapHeap.size() - 1);}return null;
}static class ScrapData {//ViewHolder是作为一个List被存进来的ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();// 缓存池中 list的大小是5,也就是每个类型的view缓存池中存储5个int mMaxScrap = 5;long mCreateRunningAverageNs = 0;long mBindRunningAverageNs = 0;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/57838.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

『赠书活动 | 第十八期』《深入浅出SSD:固态存储核心技术、原理与实战》

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; 『赠书活动 &#xff5c; 第十八期』 本期书籍&#xff1a;《深入浅出SSD&#xff1a;固态存储核心技术、原理与实战》 赠书规则&#xff1a;评论区&#xff1a;点赞&…

BPM在企业扮演什么角色?一文秒懂!

如果将企业各职能部门比作各司其职的器官组织&#xff0c;那工作流程就是将其串联为整体&#xff0c;指挥其发挥作用的中枢神经网络&#xff0c;流程作为企业管理意志的延伸&#xff0c;对企业运营管理的影响至关重要。 但随着企业IT建设的多元化&#xff0c;系统能力边界扩展&…

Java 线程池

线程池 Java 线程池是一种多线程处理技术&#xff0c;它可以在程序中预先创建一定数量的线程&#xff0c;将任务提交到线程池中&#xff0c;线程池会自动调度线程执行任务。通过使用线程池&#xff0c;可以避免反复创建和销毁线程的开销&#xff0c;提高程序性能&#xff0c;同…

monorepo更新组件报错,提示“无法加载文件 C:\Program Files\nodejs\pnpm.ps1,因为在此系统上禁止运行脚本”

解决方法&#xff1a; 第一步&#xff1a;管理员身份运行 window.powershell&#xff0c; win x打开powerShell命令框&#xff0c;进入到对应项目路径。 第二步&#xff1a;执行&#xff1a;get-ExecutionPolicy&#xff0c;显示Restricted&#xff0c;表示状态是禁止的; 第…

面试前的准备:程序员应该如何备战面试

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

盲盒电商小程序

一、准备阶段 在开始制作盲盒小程序前&#xff0c;你需要在乔拓云平台上创建一个账号&#xff0c;并登录到后台管理页面。在后台管理页面&#xff0c;你可以找到商城管理模块&#xff0c;点击进入商城编辑制作页面。 二、小程序商城模板选择与编辑 1.在商城编辑制作页面&#x…

2023.08.27 学习周报

文章目录 摘要文献阅读1.题目2.重点3.引言4.方法5.实验结果6.结论 深度学习Majorization-Minimization算法1.基本思想2.要求3.示意图 总结 摘要 This week, I read a computer science on the prediction of atmospheric pollutants in urban environments based on coupled d…

AUTOSAR汽车电子系统架构标准

AUTOSAR 目录 AUTOSAR RTE SWC和BSW SWC访问代码实现 ARXML&#xff08;AUTOSAR XML&#xff09; Interface Client-Server接口代码实现 AutoSAR OS Application AUTOSAR&#xff08;Automotive Open System Architecture&#xff09;正式发布日期是2003年&#xff0c;…

Linux系统编程:基础知识入门学习笔记汇总

Linux基础shell编程——>Linux 系统编程——>&#xff08;计算机网络&#xff09;——>Linux 网络编程 来源&#xff1a;黑马程序员-Linux系统编程 45小时 评价 这个老师好像讲了很多课程&#xff0c;都还不错我由于赶时间之前学过Linux的Shell编程和Linux的网络编程&…

swagger 2.10.5 整合 spring boot

参考&#xff1a; http://springfox.github.io/springfox/ https://github.com/springfox/springfox http://springfox.github.io/springfox/docs/current/ https://github.com/springfox/springfox-demos https://github.com/springfox/springfox-demos/tree/2.9.2 https://gi…

pandas读取excel,再写入excel

需求是这样的&#xff0c;从一个表读取数据&#xff0c;然后每次执行创建一个新表将值写入 读取这个表 写入到这个表 分别对应的是e、h列数据&#xff0c;代码如下&#xff1a; import pandas as pd import openpyxl import datetime dfpd.read_excel(rC:\Users\admin\Deskt…

设计模式-职责链模式

文章目录 职责链模式模式概述主要角色适用场景实现步骤优点注意事项 定义职责链结构示例总结 职责链模式 职责链模式是一种行为设计模式&#xff0c;它可以将请求的发送者和请求的处理者解耦&#xff0c;并按照预定义的顺序处理请求。职责链模式常用于需要逐级审批或转交处理的…

【电路设计】220V AC转低压DC电路概述

前言 最近因项目需要,电路板上要加上一个交流220V转低压直流,比如12V或者5V这种。一般来说,比较常见也比较简单的做法是使用一个变压器将220V AC进行降压,比如降到22V AC,但是很遗憾的是,支持220V的变压器一般体积很大,而板子留给电源部分的面积又非常有限,所以不得不研…

OpenHarmony组件复用示例

本文转载自《#2023盲盒码# OpenHarmony组件复用示例》&#xff0c;作者zhushangyuan_ 摘要&#xff1a;在开发应用时&#xff0c;有些场景下的自定义组件具有相同的组件布局结构&#xff0c;仅有状态变量等承载数据的差异。这样的组件缓存起来&#xff0c;需要使用到该组件时直…

C++中的运算符总结(7):复合赋值运算符

C中的运算符总结&#xff08;7&#xff09;&#xff1a;复合赋值运算符 复合赋值运算符将运算结果赋给左边的操作数。 请看下面的代码&#xff1a; int num1 22; int num2 5; num1 num2; // num1 contains 27 after the operation其中最后一行代码与下面的代码等效&#…

玩转软件|钉钉个人版内测启动:AI探索未来的工作方式

目录 前言 正文 AI为核心&#xff0c;个人效率为王&#xff01; 指令中心&#xff0c;解锁AI技巧&#xff01; 灵感Store&#xff0c;探索更多可能&#xff01; 未来的AI&#xff0c;即将问世&#xff01; 个人内测体验 前言 重磅消息&#xff1a;钉钉个人版在8月16日正…

前端面试的性能优化部分(14)每天10个小知识点

目录 系列文章目录前端面试的性能优化部分&#xff08;1&#xff09;每天10个小知识点前端面试的性能优化部分&#xff08;2&#xff09;每天10个小知识点前端面试的性能优化部分&#xff08;3&#xff09;每天10个小知识点前端面试的性能优化部分&#xff08;4&#xff09;每天…

c# modbus CRC计算器(查表法)

一、简介&#xff1a; 本案例为crc计算器&#xff0c;通过查表法计算出结果 1.窗体后台源代码 using Crc; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text…

Stm32的时钟系统以及使用SysTick滴答定时器实现延时

前言 STM32的时钟系统由多个时钟源和时钟树组成时钟源包括主时钟源&#xff08;HSE&#xff09;、内部高速时钟源&#xff08;HSI&#xff09;、内部低速时钟源&#xff08;LSI&#xff09;和外部低速时钟源&#xff08;LSE&#xff09;。时钟树由多个时钟分频器和时钟门控器组…

一道经典面试题:@Configuration 和 @Component 有何区别?

关于 Configuration 注解有一个特别经典的面试题&#xff1a; Configuration 和 Component 有什么区别&#xff1f; 无论小伙伴们之前是否背过相关的面试题&#xff0c;今天这篇文章学完之后相信大家对这个问题都会有更深一层的理解&#xff0c;废话不多少&#xff0c;咱们开…