Android ViewPager2 + FragmentStateAdapter 的使用以及问题
场景介绍:在Android业务功能开发的过程中,需要使用到嵌套ViewPage2实现页面切换,这种场景在我们的开发过程中并不少见,大致结构为一个activity包含一个viewPage2,这个viewPage2中存在一个fragment A,fragment A中也包含了一个viewPage2。所有viewPage2都使用FragmentStateAdapter 适配器实现界面数据联动。
上述实现过程并不复杂,但是在我实际业务中需要实现activity调用fragmentA中viewPage2的一些方法,当然这个需求可以使用viewModel进行实现,但是由于初版使用了方法调用,遇到了bug所以针对该功能的实现进行初步研究。
FragmentStateAdapter 介绍及简单使用
FragmentStateAdapter 是 Android Jetpack 中提供的用于管理 Fragment 的适配器,它是 RecyclerView.Adapter 的子类。
FragmentStateAdapter 会在 ViewPager 中显示的每个 Fragment 的生命周期之间进行恰当的保存和恢复 Fragment 的状态,以确保内存占用较小。
当 Fragment 不再可见时,FragmentStateAdapter 会销毁该 Fragment 的视图,但会保留其实例状态,以便在需要时重新创建。
适用于大量 Fragment 的场景,特别是数据动态变化或数据量较大的情况。该适配器的最简单使用方式如下:
adapter = new FragmentStateAdapter(getChildFragmentManager(), getLifecycle()) {@NonNull@Overridepublic Fragment createFragment(int position) {return fragments.get(position);}@Overridepublic int getItemCount() {return fragments.size();}};
fragment切换销毁
在默认情况下,viewPage2提供的性能优化实现了临近一个fragment预加载机制,及如果初始展示第0个fragment,viewPage2也会把第1个fragment进行创建视图但并不展示。也就是说viewPage2默认的缓存机制会缓存三个fragment,一旦需要缓存的实例超过三个,例如从第0个滑动到第2个,则会缓存123位置的fragment,响应的第0个fragment将被销毁,一直执行到onDestroy()生命周期。
值得说明的是:销毁仅仅代表了生命周期的结束,默认情况下该fragment的实例、其内部成员变量以及其绑定的视图都不一定会消失。 基于这一原因,为了防止内存溢出我们在onDestroy()生命周期一般会针对成员变量进行setNull操作。通过setNull可以将成员变量消除引用,以便触发GC。接触过java都清楚即便没有引用的变量也未必里面会触发GC,因此当我门将Binding设置成null后,其关联的view也未必会里面消失,在fragment在此展示时,依旧有可能调用上次绘制过的view进行显示。而且在通过viewPage2切换导致fragment销毁的过程中,其本质上是执行到了onDestroy()生命周期,并不见得会销毁视图,而且viewPage2还将保存一个该fragment的实例!根据上述内容可以总结下面几点:
- 进入onDestroy生命周期并不能一定是成员变量销毁。
- 通过viewPage2切换导致fragment销毁本质上是让fragment执行到onDestroy()生命周期,但是viewPage2还保存了该fragment的实例
- 如果在onDestroy()生命周期还没有把该fragment成员变量setNull,则viewPage2所持有的该fragment对象依旧保留着这些fragment成员变量
- 在onDestroy()生命周期中将Binding设置成null后并不能将其view都进行清空
fragment展示
展示通常有三种,一种是viewPage2内缓存的fragment复现,一种是新的未展示过的fragment展示,还有一种是被销毁了的fragment的展示:他们对应一下过程:
- 缓存内fragment展示:执行onResume()后直接进行展示
- 未展示过的fragment展示:调用构造方法初始化实例 – 调用onCreate一直执行到onResume生命周期
- 销毁的fragment重新展示:调用onCreateView一直执行到onResume进行展示。
需要注意的是销毁的fragment重新展示的过程中并没有进行fragment实例创建,因此本质上viewPage2已经拥有该实例了,知识当时调用了onDestroy方法而已。
我的问题
在我的业务场景中,需要使用到viewPage2下的fragment实例,然后调用该实例的方法,如果只是单层viewPage2的使用,则相对比较简单,但是如果是嵌套viewPage2则会出现以下问题:
一旦持有viewPage2的fragment,在其所属的viewPage2切换过程中销毁了,然后又由销毁状态到复现,此时通过上述FragmentStateAdapter设置的fragment回调会导致异常。
在适配器的实现过程中,我们通过fragments【list】进行fragment对象持有,如果fragmentA【持有viewPage2的那个fragment】被复现时,如果我们在oncreateView生命周期对fragments进行初始化,调用add(fragment)方法,那么此时复现导致fragments持有对象和上次展示时其所持有对象不同!在fragmentA复现过程中必然也进行着fragmentA所持有的viewPage2下的fragment复现,刚才已经说了销毁的复现本质上是oncreate生命周期的重新调用,此时调用的是原来持有fragment对象的oncreate生命周期,而在fragmentA复现的过程中导致fragments持有的对象和历史对象不同,这些对象严格来讲仅仅经历了对象实例化阶段,未执行fragment的其他生命周期,还未创建持有视图,如果我们调用视图的相关操作则会导致空指针等异常情况!
简单来说就是fragmentA的销毁并不会导致其持有的viewPage2的销毁,更不会导致viewPage2所持有的fragment的销毁,如果我们对fragments进行重新设置,此时创建的fragment对象仅仅创建对象而已。
viewPage2的setAdapter
按照我的问题描述,那么是不是我将viewPage2原先持有的fragment对象全都删除就能解决问题,删除的途径是调用viewPage2的setAdapter(null)方法。很遗憾,该方法并不能解决问题,该方法的源码如下:
public void setAdapter(@Nullable @SuppressWarnings("rawtypes") Adapter adapter) {final Adapter<?> currentAdapter = mRecyclerView.getAdapter();mAccessibilityProvider.onDetachAdapter(currentAdapter);unregisterCurrentItemDataSetTracker(currentAdapter);mRecyclerView.setAdapter(adapter);mCurrentItem = 0;restorePendingState();mAccessibilityProvider.onAttachAdapter(adapter);registerCurrentItemDataSetTracker(adapter);}
在该方法执行的过程中restorePendingState的源码如下:
private void restorePendingState() {if (mPendingCurrentItem == NO_POSITION) {// No state to restore, or state is already restoredreturn;}Adapter<?> adapter = getAdapter();if (adapter == null) {return;}if (mPendingAdapterState != null) {if (adapter instanceof StatefulAdapter) {((StatefulAdapter) adapter).restoreState(mPendingAdapterState);}mPendingAdapterState = null;}// Now we have an adapter, we can clamp the pending current item and set itmCurrentItem = Math.max(0, Math.min(mPendingCurrentItem, adapter.getItemCount() - 1));mPendingCurrentItem = NO_POSITION;mRecyclerView.scrollToPosition(mCurrentItem);mAccessibilityProvider.onRestorePendingState();}/**restoreState方法如下**/@Overridepublic final void restoreState(@NonNull Parcelable savedState) {for (String key : bundle.keySet()) {if (isValidKey(key, KEY_PREFIX_FRAGMENT)) {long itemId = parseIdFromKey(key, KEY_PREFIX_FRAGMENT);Fragment fragment = mFragmentManager.getFragment(bundle, key);mFragments.put(itemId, fragment);continue;}if (isValidKey(key, KEY_PREFIX_STATE)) {long itemId = parseIdFromKey(key, KEY_PREFIX_STATE);Fragment.SavedState state = bundle.getParcelable(key);if (containsItem(itemId)) {mSavedStates.put(itemId, state);}continue;}throw new IllegalArgumentException("Unexpected key in savedState: " + key);}}
需要注意mPendingAdapterState 这一变量,该变量将保留了历史fragment的基本信息,因此在setAdapter的方法过程中还会将viewPage2的一些信息设置到你新的adapter中,是不是很尴尬?setAdapter方法并不是简单的把adapter方法设置后就结束了,viewPage2内部还将自己历史关心的数据设置到该adapter中!
FragmentStateAdapter 的createFragment源码如下:
private void ensureFragment(int position) {long itemId = getItemId(position);if (!mFragments.containsKey(itemId)) {// TODO(133419201): check if a Fragment provided here is a new FragmentFragment newFragment = createFragment(position);newFragment.setInitialSavedState(mSavedStates.get(itemId));mFragments.put(itemId, newFragment);}}
mFragments对象对于adapter很重要,该对象持有了历史创建的fragment,这样就导致无需每次使用的过程中进行重复创建了,但这会导致一个尴尬的问题,该mFragments默认查找是按照位置进行查找的,换句话说一旦viewPage2完成展示以及数据加载,在后续的切换过程中,就算你用createFragment可以创建fragment对象,但是由于相同位置下mFragments中已经存在数据,所以根部不会执行createFragment方法!
至此闭环:setAdapter方法会使用到viewPage2持有的savedState设置adapter的mFragments对象,ensureFragment方法会根据mFragments按照position判断fragment是否存在!到此结束。
总结
本文内容描述比较粗略,主要讲述了viewPage2嵌套使用过程中的一些问题以及导致这些问题的原因,总结起来无非以下几点:
- viewPage2销毁fragment后依旧会持有其对象信息,并标记在adapter中的mFragments中,在后续复现时不会再进行对象的创建
- 将Binding设置成null并不一定会导致viewPage2的重绘,其依旧可能保留自己原始数据。
- viewPape2在进行setAdapter方法的过程中会将自己持有的fragment对象标记信息设置到FragmentStateAdapter 的mFragments中。
相关文章:
Android ViewPager2 + FragmentStateAdapter 的使用以及问题
场景介绍:在Android业务功能开发的过程中,需要使用到嵌套ViewPage2实现页面切换,这种场景在我们的开发过程中并不少见,大致结构为一个activity包含一个viewPage2,这个viewPage2中存在一个fragment A,fragme…...
FPGA中的乒乓操作
为什么不直接选用一个缓存更大的FIFO而选用乒乓操作为什么乒乓操作可以实现低速处理高速数据乒乓操作适用哪些场景 一、乒乓操作结构 首先先介绍一下乒乓操作的原理,其结构如下: 输入选择单元负责将数据送到数据缓冲模块,然后输出选择单元负…...
gnocchi学习小结
背景 总结gnocchi 4.4版本gnocchi-metricd工作流程 入口 gnocchi.cli.metricd metricd stop after processing metric默认为0,调servicemanager run MetricdServiceManager __init__ 服务逻辑封装到MetricdServiceManager初始化中 主要由MetricProcessor, Met…...
【机器学习】Pandas中to_pickle()函数的介绍与机器学习中的应用
【机器学习】Pandas中to_pickle()函数的介绍和机器学习中的应用 🌈 欢迎莅临我的个人主页👈这里是我深耕Python编程、机器学习和自然语言处理(NLP)领域,并乐于分享知识与经验的小天地!🎇 &#…...
lightning的hook顺序
结果 setup: 训练循环开始前设置数据加载器和模型。 configure_optimizers: 设置优化器和学习率调度器。 on_fit_start: 训练过程开始。 on_train_start: 训练开始。 on_train_epoch_start: 每个训练周期开始。 on_train_batch_start: 每个训练批次开始。 on_before_bac…...
【ARFoundation自学03】AR Point Cloud 点云(参考点标记)功能详解
和平面识别框架一样 1为XR Origin添加AR Point Cloud Manager组件 然后你的ar应用就具备了点云识别功能,就这么简单 2.可视化这些云点 创建一个美术效果的预制体,人家提供了预设模板 然后拖到仓库(ASSETS)创建预制体ÿ…...
x264 码率控制中实现 VBV 算法源码分析
关于 VBV 的解释与原理可以参考x264 码率控制 VBV 原理。 x264中 VBV 算法执行的流程 vbv 参数配置相关函数 x264_param_default函数 功能:编码参数默认设置,关于 vbv的参数的默认设置;函数内vbv相关代码:/* ... */ //代码有删减 param->rc.i_vbv_max_bitrate = 0; par…...
宝兰德入选“鑫智奖·2024金融数据智能运维创新优秀解决方案”榜单
近日,由金科创新社主办、全球金融专业人士协会支持的“2024 鑫智奖第六届金融数据智能优秀解决方案”评选结果正式公布。凭借卓越的技术实力和方案能力,宝兰德「智能全链路性能监控解决方案」从90个参选方案中脱颖而出,荣誉入选“鑫智奖2024金…...
Unity3D雨雪粒子特效(Particle System)
系列文章目录 unity工具 文章目录 系列文章目录👉前言👉一、下雨的特效1-1.首先就是创建一个自带的粒子系统,整几张贴图,设置一下就能实现想要的效果了1-2 接着往下看视频效果 👉二、下雪的特效👉三、下雪有积雪的效果3-1 先把控…...
记录使用自定义编辑器做试题识别功能
习惯了将解析写在代码注释,这里就直接上代码啦,里面用到的bxm-ui3组件库是博主基于element-Plus做的,可以通过npm i bxm-ui3自行安装使用 // 识别方法: // dom 当前识别数据所在区域, questionType 当前点击编辑选择的题目类型&a…...
MySQL索引和视图
MySQL索引和视图是关系型数据库MySQL中的两个重要概念。索引用于优化数据库的查询性能,而视图用于提供一个逻辑上的表结构,方便用户查询和操作数据。 索引是一种数据结构,可以加速对数据库表中的数据进行查询的速度。通过创建索引࿰…...
Java单元测试Mock的用法,关于接口测试的用例
Testvoid getAllTradeDateList() {// 创建模拟对象Bc6CalculateService calculateService Mockito.mock(Bc6CalculateService.class);String allTradeDateListStr ExcelUtil.excelToJsonStr("bc6/NibTradeDate.xlsx");// 设置模拟行为List<NibTradeDateCloudDto…...
《心理学报》文本分析技术最新进展总结盘点
这些研究展示了文本分析在多个心理学领域内的强大应用,包括情境判断测验的自动化评分、自闭症儿童教育干预的学习效果评估、中文文本阅读的词切分和词汇识别机制、网络突发事件的负性偏向分析,以及小学生羞怯特质的预测与语言风格模型构建。通过采用机器…...
json格式文件备份redis数据库 工具
背景: 项目组要求使用 json备份redis缓存数据库内容。 附件里工具是一个包含redis-dump工具的镜像文件,方便用户在局域网中使用容器备份redis缓存数据库。 使用步骤: 解压tar文件,导入镜像 docker load < redis_dump_of_my…...
JAVA系列:NIO
NIO学习 一、前言 先来看一下NIO的工作流程图: NIO三大核心组件,channel(通道)、Buffer(缓冲区)、selector(选择器)。NIO利用的是多路复用模型,一个线程处理多个IO的读…...
偏微分方程算法之抛物型方程差分格式编程示例二
目录 一、研究问题 二、C++代码 三、结果分析 一、研究问题 采用向后欧拉格式计算抛物型方程初边值问题:...
linux 查看 线程名, 线程数
ps -T -p 3652 ps H -T <PID> ps -eLf | grep process_name top -H -p <pid> 查看进程创建的所有线程_ps 显示一个进程的所有线程名字-CSDN博客...
python class __getattr__ 与 __getattribute__ 的区别
在Python中,__getattr__是一个特殊的方法,用于处理访问不存在的属性时的行为。它通常在类中被重写,以便在属性访问失败时提供自定义的处理逻辑。 __getattr__ 的使用 1. 基本用法 __getattr__方法在访问类实例的某个不存在的属性时自动调用…...
[ C++ ] 类和对象( 下 )
初始化列表 初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟 一个放在括号中的初始值或表达式。 class Date { public: Date(int year, int month, int day): _year(year), _month(month), _d…...
这么多不同接口的固态硬盘,你选对了嘛!
固态硬盘大家都不陌生,玩游戏、办公存储都会用到。如果自己想要给电脑或笔记本升级下存储,想要存储更多的文件,该怎么选购不同类型的SSD固态盘呐,下面就来认识下日常使用中常见的固态硬盘。 固态硬盘(Solid State Drive, SSD)作为数据存储技术的革新力量,其接口类型的选…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
