当前位置: 首页 > news >正文

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 的使用以及问题

场景介绍&#xff1a;在Android业务功能开发的过程中&#xff0c;需要使用到嵌套ViewPage2实现页面切换&#xff0c;这种场景在我们的开发过程中并不少见&#xff0c;大致结构为一个activity包含一个viewPage2&#xff0c;这个viewPage2中存在一个fragment A&#xff0c;fragme…...

FPGA中的乒乓操作

为什么不直接选用一个缓存更大的FIFO而选用乒乓操作为什么乒乓操作可以实现低速处理高速数据乒乓操作适用哪些场景 一、乒乓操作结构 首先先介绍一下乒乓操作的原理&#xff0c;其结构如下&#xff1a; 输入选择单元负责将数据送到数据缓冲模块&#xff0c;然后输出选择单元负…...

gnocchi学习小结

背景 总结gnocchi 4.4版本gnocchi-metricd工作流程 入口 gnocchi.cli.metricd metricd stop after processing metric默认为0&#xff0c;调servicemanager run MetricdServiceManager __init__ 服务逻辑封装到MetricdServiceManager初始化中 主要由MetricProcessor, Met…...

【机器学习】Pandas中to_pickle()函数的介绍与机器学习中的应用

【机器学习】Pandas中to_pickle()函数的介绍和机器学习中的应用 &#x1f308; 欢迎莅临我的个人主页&#x1f448;这里是我深耕Python编程、机器学习和自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;并乐于分享知识与经验的小天地&#xff01;&#x1f387; &#…...

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应用就具备了点云识别功能&#xff0c;就这么简单 2.可视化这些云点 创建一个美术效果的预制体&#xff0c;人家提供了预设模板 然后拖到仓库&#xff08;ASSETS&#xff09;创建预制体&#xff…...

x264 码率控制中实现 VBV 算法源码分析

关于 VBV 的解释与原理可以参考x264 码率控制 VBV 原理。 x264中 VBV 算法执行的流程 vbv 参数配置相关函数 x264_param_default函数 功能:编码参数默认设置,关于 vbv的参数的默认设置;函数内vbv相关代码:/* ... */ //代码有删减 param->rc.i_vbv_max_bitrate = 0; par…...

宝兰德入选“鑫智奖·2024金融数据智能运维创新优秀解决方案”榜单

近日&#xff0c;由金科创新社主办、全球金融专业人士协会支持的“2024 鑫智奖第六届金融数据智能优秀解决方案”评选结果正式公布。凭借卓越的技术实力和方案能力&#xff0c;宝兰德「智能全链路性能监控解决方案」从90个参选方案中脱颖而出&#xff0c;荣誉入选“鑫智奖2024金…...

Unity3D雨雪粒子特效(Particle System)

系列文章目录 unity工具 文章目录 系列文章目录&#x1f449;前言&#x1f449;一、下雨的特效1-1.首先就是创建一个自带的粒子系统,整几张贴图,设置一下就能实现想要的效果了1-2 接着往下看视频效果 &#x1f449;二、下雪的特效&#x1f449;三、下雪有积雪的效果3-1 先把控…...

记录使用自定义编辑器做试题识别功能

习惯了将解析写在代码注释&#xff0c;这里就直接上代码啦&#xff0c;里面用到的bxm-ui3组件库是博主基于element-Plus做的&#xff0c;可以通过npm i bxm-ui3自行安装使用 // 识别方法&#xff1a; // dom 当前识别数据所在区域, questionType 当前点击编辑选择的题目类型&a…...

MySQL索引和视图

MySQL索引和视图是关系型数据库MySQL中的两个重要概念。索引用于优化数据库的查询性能&#xff0c;而视图用于提供一个逻辑上的表结构&#xff0c;方便用户查询和操作数据。 索引是一种数据结构&#xff0c;可以加速对数据库表中的数据进行查询的速度。通过创建索引&#xff0…...

Java单元测试Mock的用法,关于接口测试的用例

Testvoid getAllTradeDateList() {// 创建模拟对象Bc6CalculateService calculateService Mockito.mock(Bc6CalculateService.class);String allTradeDateListStr ExcelUtil.excelToJsonStr("bc6/NibTradeDate.xlsx");// 设置模拟行为List<NibTradeDateCloudDto…...

《心理学报》文本分析技术最新进展总结盘点

这些研究展示了文本分析在多个心理学领域内的强大应用&#xff0c;包括情境判断测验的自动化评分、自闭症儿童教育干预的学习效果评估、中文文本阅读的词切分和词汇识别机制、网络突发事件的负性偏向分析&#xff0c;以及小学生羞怯特质的预测与语言风格模型构建。通过采用机器…...

json格式文件备份redis数据库 工具

背景&#xff1a; 项目组要求使用 json备份redis缓存数据库内容。 附件里工具是一个包含redis-dump工具的镜像文件&#xff0c;方便用户在局域网中使用容器备份redis缓存数据库。 使用步骤&#xff1a; 解压tar文件&#xff0c;导入镜像 docker load < redis_dump_of_my…...

JAVA系列:NIO

NIO学习 一、前言 先来看一下NIO的工作流程图&#xff1a; NIO三大核心组件&#xff0c;channel&#xff08;通道&#xff09;、Buffer&#xff08;缓冲区&#xff09;、selector&#xff08;选择器&#xff09;。NIO利用的是多路复用模型&#xff0c;一个线程处理多个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中&#xff0c;__getattr__是一个特殊的方法&#xff0c;用于处理访问不存在的属性时的行为。它通常在类中被重写&#xff0c;以便在属性访问失败时提供自定义的处理逻辑。 __getattr__ 的使用 1. 基本用法 __getattr__方法在访问类实例的某个不存在的属性时自动调用…...

[ C++ ] 类和对象( 下 )

初始化列表 初始化列表&#xff1a;以一个冒号开始&#xff0c;接着是一个以逗号分隔的数据成员列表&#xff0c;每个"成员变量"后面跟 一个放在括号中的初始值或表达式。 class Date { public: Date(int year, int month, int day): _year(year), _month(month), _d…...

这么多不同接口的固态硬盘,你选对了嘛!

固态硬盘大家都不陌生,玩游戏、办公存储都会用到。如果自己想要给电脑或笔记本升级下存储,想要存储更多的文件,该怎么选购不同类型的SSD固态盘呐,下面就来认识下日常使用中常见的固态硬盘。 固态硬盘(Solid State Drive, SSD)作为数据存储技术的革新力量,其接口类型的选…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢

随着互联网技术的飞速发展&#xff0c;消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁&#xff0c;不仅优化了客户体验&#xff0c;还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用&#xff0c;并…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命

在华东塑料包装行业面临限塑令深度调整的背景下&#xff0c;江苏艾立泰以一场跨国资源接力的创新实践&#xff0c;重新定义了绿色供应链的边界。 跨国回收网络&#xff1a;废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点&#xff0c;将海外废弃包装箱通过标准…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论

路径问题的革命性重构&#xff1a;基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中&#xff08;图1&#xff09;&#xff1a; mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

git: early EOF

macOS报错&#xff1a; Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...

C++实现分布式网络通信框架RPC(2)——rpc发布端

有了上篇文章的项目的基本知识的了解&#xff0c;现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...