【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;点赞&…

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…

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;并按照预定义的顺序处理请求。职责链模式常用于需要逐级审批或转交处理的…

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

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

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;咱们开…

容器和宿主机之间的存储问题

简介 作用&#xff1a;方便备份恢复数据&#xff0c;实现数据共享 一、单台机器中 用数据卷挂载 二、多台机器中 ssh 建立免密通道将数据scp过去&#xff0c;然后再用卷挂载到容器内 nfs 网络文件系统 搭建nfs服务器客户端挂载 过程如下 一.安装软件包yum install -…

新能源汽车技术的最新进展和未来趋势

文章目录 电池技术的进步智能驾驶与自动驾驶技术充电基础设施建设新能源汽车共享和智能交通未来趋势展望结论 &#x1f389;欢迎来到AIGC人工智能专栏~探索新能源汽车技术的最新进展和未来趋势 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&#xff1a;IT陈寒的博客…

融媒行业落地客户旅程编排,详解数字化用户运营实战

移动互联网时代是流量红利的时代&#xff0c;企业常用低成本的方式进行获客&#xff0c;“增长黑客”的概念大范围传播。与此同时&#xff0c;机构媒体受到传播环境的影响&#xff0c;也开始启动全行业的媒体融合转型。在此背景下&#xff0c;2015 年神策数据成立&#xff0c;核…

港联证券:为什么好股票拿不住?股票怎么买更赚钱?

股票是一种高危险高收益的出资方式&#xff0c;要想挣钱仍是需求掌握一些技巧。那么为什么好股票拿不住&#xff1f;股票怎样买更挣钱&#xff1f;港联证券也为大家准备了相关内容&#xff0c;以供参考。 为什么好股票拿不住&#xff1f; 1、心态不稳。许多出资者缺少长时间的…

Java版工程行业管理系统源码-专业的工程管理软件-提供一站式服务 em

​ 鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部工…

bootloader串口更新程序[瑕疵学习板]

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、储备知识二、程序步骤2.程序展示1.bootloader2.然后是主运行函数总结前言 很久没有更新文章了。最近工作太忙,没有学习很多的知识,然后这两天不忙了,就学习了一下bootloader的程序升级…

【已解决】pycharm突然双击无法打开,重启电脑也不管用

1.问题&#xff1a; pycharm突然双击无法打开&#xff0c;重启电脑也不管用 2.解决 2.1 方法一&#xff08;修改Roaming&#xff09; 1.找到C盘对应路径下的pycharm版本 2. 用记事本打开文件类型为VMOPTIONS文件 3. 修改或删除最后一行的映射路径 4.保存退出 2.2 方法二…

2023.8.28日论文阅读

文章目录 NestFuse: An Infrared and Visible Image Fusion Architecture based on Nest Connection and Spatial/Channel Attention Models(2020的论文)本文方法 LRRNet: A Novel Representation Learning Guided Fusion Network for Infrared and Visible Images本文方法学习…