activity 点击后传递数据给fragment_Fragment 新特性 : Fragment Result API 使用以及源码分析
- 原标题: Android Fragments: Fragment Result
- 原文地址: https://proandroiddev.com/android-fragments-fragment-result......
- 原文作者: Husayn Hakeem
今年 Google 推出了 Fragment Result API
和 Activity Results API
,用来取代之前的 Activity 和 Fragment 之间通信方式的不足。
这篇文章大概是我在 5 月份的写的,主要介绍 Fragment Result API
,分为 译文 和 译者的思考 两个部分。
Fragment Result API
主要介绍 Fragment 间通信的新方式,是在 Fragment 1.3.0-alpha04
新增加的 API ,而现在最新版本已经到 fragment-1.3.0-beta01
应该很快就能应用在项目里面了。
接下来分析一下 Fragment Result API
主要为我们解决了什么问题,它都有那些更新。
通过这篇文章你将学习到以下内容,将在译者思考部分会给出相应的答案
- 新 Fragment 间通信的方式的使用?
- 新 Fragment 间通信的源码分析?
- 汇总 Fragment 之间的通信的方式?
译文
Frrgament 间传递数据可以通过多种方式,包括使用 target Fragment APIs (Fragment.setTargetFragment()
和 Fragment.getTargetFragment()
),ViewModel
或者 使用 Fragments’ 父容器 Activity,target Fragment APIs 已经过时了,现在鼓励使用新的 Fragment result APIs 完成 Frrgament 之间传递数据,其中传递数据由 FragmentManager 处理,并且在 Fragments 设置发送数据和接受数据
在 Frrgament 之间传递数据
使用新的 Fragment APIs 在 两个 Frrgament 之间的传递,没有任何引用,可以使用它们公共的 FragmentManager,它充当 Frrgament 之间传递数据的中心存储。
接受数据
如果想在 Fragment 中接受数据,可以在 FragmentManager 中注册一个 FragmentResultListener,参数 requestKey 可以过滤掉 FragmentManager 发送的数据
FragmentManager.setFragmentResultListener(requestKey,lifecycleOwner,FragmentResultListener { requestKey: String, result: Bundle ->// Handle result})
参数 lifecycleOwner 可以观察生命周期,当 Fragment 的生命周期处于 STARTED 时接受数据。如果监听 Fragment 的生命周期,您可以在接收到新数据时安全地更新 UI,因为 view 的创建(onViewCreated() 方法在 onStart() 之前被调用)。
当生命周期处于 LifecycleOwner STARTED 的状态之前,如果有多个数据传递,只会接收到最新的值
当生命周期处于 LifecycleOwner DESTROYED 时,它将自动移除 listener,如果想手动移除 listener,需要调用 FragmentManager.setFragmentResultListener() 方法,传递空的 FragmentResultListener
在 FragmentManager 中注册 listener,依赖于 Fragment 发送返回的数据
- 如果在 FragmentA 中接受 FragmentB 发送的数据,FragmentA 和 FragmentB 处于相同的层级,通过 parent FragmentManager 进行通信,FragmentA 必须使用 parent FragmentManager 注册 listener
parentFragmentManager.setFragmentResultListener(...)
- 如果在 FragmentA 中接受 FragmentB 发送的数据,FragmentA 是 FragmentB 的父容器, 他们通过 child FragmentManager 进行通信
childFragmentManager.setFragmentResultListener(...)
listener 必须设置的Fragment 相同的 FragmentManager
发送数据
如果 FragmentB 发送数据给 FragmentA,需要在 FragmentA 中注册 listener,通过 parent FragmentManager 发送数据
parentFragmentManager.setFragmentResult(requestKey, // Same request key FragmentA used to register its listenerbundleOf(key to value) // The data to be passed to FragmentA
)
测试 Fragment Results
测试 Fragment 是否成功接收或发送数据,可以使用 FragmentScenario API
接受数据
如果在 FragmentA 中注册 FragmentResultListener 接受数据,你可以模拟 parent FragmentManager 发送数据,如果在 FragmentA 中正确注册了 listener,可以用来验证 FragmentA 是否能收到数据,例如,如果在 FragmentA 中接受数据并更新 UI, 可以使用 Espresso APIs 来验证是否期望的数据
@Test
fun shouldReceiveData() {val scenario = FragmentScenario.launchInContainer(FragmentA::class.java)// Pass data using the parent fragment managerscenario.onFragment { fragment ->val data = bundleOf(KEY_DATA to "value")fragment.parentFragmentManager.setFragmentResult("aKey", data)}// Verify data is received, for example, by verifying it's been displayed on the UIonView(withId(R.id.textView)).check(matches(withText("value")))
}
发送数据
可以在 FragmentB 的 parent FragmentManager 上注册一个 FragmentResultListener 来测试 FragmentB 是否成功发送数据,当发送数据结束时,可以来验证这个 listener 是否能收到数据
@Test
fun shouldSendData() {val scenario = FragmentScenario.launchInContainer(FragmentB::class.java)// Register result listenervar receivedData = ""scenario.onFragment { fragment ->fragment.parentFragmentManager.setFragmentResultListener(KEY,fragment,FragmentResultListener { key, result ->receivedData = result.getString(KEY_DATA)})}// Send dataonView(withId(R.id.send_data)).perform(click())// Verify data was successfully sentassertThat(receivedData).isEqualTo("value")
}
总结
虽然使用了 Fragment result APIs,替换了过时的 Fragment target APIs,但是新的 APIs 在Bundle 作为数据传传递方面有一些限制,只能传递简单数据类型、Serializable 和 Parcelable 数据,Fragment result APIs 允许程序从崩溃中恢复数据,而且不会持有对方的引用,避免当 Fragment 处于不可预知状态的时,可能发生未知的问题
译者的思考
这是译者的一些思考,总结一下 Fragment 1.3.0-alpha04 新增加的 Fragment 间通信的 API
数据接受
FragmentManager.setFragmentResultListener(requestKey,lifecycleOwner,FragmentResultListener { requestKey: String, result: Bundle ->// Handle result})
数据发送
parentFragmentManager.setFragmentResult(requestKey, // Same request key FragmentA used to register its listenerbundleOf(key to value) // The data to be passed to FragmentA
)
那么 Fragment 间通信的新 API 给我们带来哪些好处呢:
- 在 Fragment 之间传递数据,不会持有对方的引用
- 当生命周期处于 ON_START 时开始处理数据,避免当 Fragment 处于不可预知状态的时,可能发生未知的问题
- 当生命周期处于 ON_DESTROY 时,移除监听
我们一起来从源码的角度分析一下 Google 是如何做的
源码分析
按照惯例从调用的方法来分析,数据接受时,调用了 FragmentManager 的 setFragmentResultListener 方法
androidx.fragment/fragment/1.3.0-alpha04......androidx/fragment/app/FragmentManager.java
private final ConcurrentHashMap<String, LifecycleAwareResultListener> mResultListeners =new ConcurrentHashMap<>();@Override
public final void setFragmentResultListener(@NonNull final String requestKey,@NonNull final LifecycleOwner lifecycleOwner,@Nullable final FragmentResultListener listener) {// mResultListeners 是 ConcurrentHashMap 的实例,用来储存注册的 listener// 如果传递的参数 listener 为空时,移除 requestKey 对应的 listenerif (listener == null) {mResultListeners.remove(requestKey);return;}// Lifecycle是一个生命周期感知组件,一般用来响应Activity、Fragment等组件的生命周期变化final Lifecycle lifecycle = lifecycleOwner.getLifecycle();// 当生命周期处于 DESTROYED 时,直接返回// 避免当 Fragment 处于不可预知状态的时,可能发生未知的问题if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {return;}// 开始监听生命周期LifecycleEventObserver observer = new LifecycleEventObserver() {@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {// 当生命周期处于 ON_START 时开始处理数据if (event == Lifecycle.Event.ON_START) {// 开始检查受到的数据Bundle storedResult = mResults.get(requestKey);if (storedResult != null) {// 如果结果不为空,调用回调方法listener.onFragmentResult(requestKey, storedResult);// 清除数据setFragmentResult(requestKey, null);}}// 当生命周期处于 ON_DESTROY 时,移除监听if (event == Lifecycle.Event.ON_DESTROY) {lifecycle.removeObserver(this);mResultListeners.remove(requestKey);}}};lifecycle.addObserver(observer);mResultListeners.put(requestKey, new FragmentManager.LifecycleAwareResultListener(lifecycle, listener));
}
- Lifecycle是一个生命周期感知组件,一般用来响应Activity、Fragment等组件的生命周期变化
- 获取 Lifecycle 去监听 Fragment 的生命周期的变化
- 当生命周期处于 ON_START 时开始处理数据,避免当 Fragment 处于不可预知状态的时,可能发生未知的问题
- 当生命周期处于 ON_DESTROY 时,移除监听
接下来一起来看一下数据发送的方法,调用了 FragmentManager 的 setFragmentResult 方法
androidx.fragment/fragment/1.3.0-alpha04......androidx/fragment/app/FragmentManager.java
private final ConcurrentHashMap<String, Bundle> mResults = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, LifecycleAwareResultListener> mResultListeners =new ConcurrentHashMap<>();@Override
public final void setFragmentResult(@NonNull String requestKey, @Nullable Bundle result) {if (result == null) {// mResults 是 ConcurrentHashMap 的实例,用来存储数据传输的 Bundle// 如果传递的参数 result 为空,移除 requestKey 对应的 BundlemResults.remove(requestKey);return;}// mResultListeners 是 ConcurrentHashMap 的实例,用来储存注册的 listener// 获取 requestKey 对应的 listenerLifecycleAwareResultListener resultListener = mResultListeners.get(requestKey);if (resultListener != null && resultListener.isAtLeast(Lifecycle.State.STARTED)) {// 如果 resultListener 不为空,并且生命周期处于 STARTED 状态时,调用回调resultListener.onFragmentResult(requestKey, result);} else {// 否则保存当前传输的数据mResults.put(requestKey, result);}
}
- 获取 requestKey 注册的 listener
- 当生命周期处于 STARTED 状态时,开始发送数据
- 否则保存当前传输的数据
源码分析到这里结束了,我们一起来思考一下,在之前我们的都有那些数据传方式
汇总 Fragment 之间的通信的方式
- 通过共享 ViewModel 或者关联 Activity来完成,Fragment 之间不应该直接通信 参考 Google: ViewModel#sharing
- 通过接口,可以在 Fragment 定义接口,并在 Activity 实现它 参考 Google: 与其他 Fragment 通信
- 通过使用 findFragmentById 方法,获取 Fragment 的实例,然后调用 Fragment 的公共方法 参考 Google: 与其他 Fragment 通信
- 调用 Fragment.setTargetFragment() 和 Fragment.getTargetFragment() 方法,但是注意 target fragment 需要直接访问另一个 fragment 的实例,这是十分危险的,因为你不知道目标 fragment 处于什么状态
- Fragment 新的 API, setFragmentResult() 和 setFragmentResultListener()
综合以上通信方式,那么你认为 Fragment 之间通信最好的方式是什么?
参考文献
- Now in Android #17: https://medium.com/androiddeve......
- Pass data between fragments: https://developer.android.com/training/basi......
- ViewModel#sharing: https://developer.android.com/topic/librari......
- 与其他 Fragment 通信: https://developer.android.com/training/basic......
结语
全文到这里就结束了,如果有帮助 点个赞 就是对我最大的鼓励!
致力于分享一系列 Android 系统源码、逆向分析、算法、翻译、Jetpack 源码相关的文章,在技术的道路上一起前进
最后推荐我一直在更新维护的项目和网站:
- 计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看:
AndroidX-Jetpack-Practicegithub.com
- LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析
- 剑指 offer 及国内外大厂面试题解:
剑指Offer题解offer.hi-dhl.com
- LeetCode 系列题解:
LeetCode 系列题解leetcode.hi-dhl.com
- 最新 Android 10 源码分析系列文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,仓库持续更新,欢迎前去查看
hi-dhl/Android10-Source-Analysisgithub.com
- 整理和翻译一系列精选国外的技术文章,每篇文章都会有译者思考部分,对原文的更加深入的解读,仓库持续更新,欢迎前去查看
hi-dhl/Technical-Article-Translationgithub.com
- 「为互联网人而设计,国内国外名站导航」涵括新闻、体育、生活、娱乐、设计、产品、运营、前端开发、Android 开发等等网址,欢迎前去查看
Hi World | 为互联网人而设计的国内国外名站导航site.51git.cn
activity 点击后传递数据给fragment_Fragment 新特性 : Fragment Result API 使用以及源码分析相关推荐
- activity 点击后传递数据给fragment_Fragment 的过去、现在和将来
Fragment 是 Android 中历史十分悠久的一个组件,它在 API 11 被加入,时至今日已成为 Android 开发中最常用的组件之一.Fragment 有了哪些新特性.修复了哪些问题,都 ...
- activity 点击后传递数据给fragment_ViewModel+LiveData实现Fragment间通信
在前面的文章中,我们已经知道,ViewModel能够将数据从Activity中剥离出来.只要Activity不被销毁,ViewModel会一直存在,并且独立于Activity的配置变化,即旋转屏幕导致 ...
- arraylist扩容是创建新数组吗 java_Java集合干货——ArrayList源码分析
前言 在之前的文章中我们提到过ArrayList,ArrayList可以说是每一个学java的人使用最多最熟练的集合了,但是知其然不知其所以然.关于ArrayList的具体实现,一些基本的都也知道,譬 ...
- activity 点击后传递数据给fragment_【磨叽教程】Android进阶之Fragment的管理以及事务执行...
导读:本文大约2000字,预计阅读时间3分钟.本文纯属技术文,无推广. 正文 如要管理 Activity 中的Fragment,需使用 FragmentManager.如要获取它,请在Activity ...
- 返璞归真 asp.net mvc (10) - asp.net mvc 4.0 新特性之 Web API
返璞归真 asp.net mvc (10) - asp.net mvc 4.0 新特性之 Web API 原文:返璞归真 asp.net mvc (10) - asp.net mvc 4.0 新特性之 ...
- 使用 Bundle在Activity间传递数据
使用 Intent 启动另一个 Activity Intent showNextPage_Intent=new new new new Intent(); showNextPage_I ...
- android 不同activity之间传递数据
1> 不同activity之间传递数据: Intent intent=new Intent(); intent.setClass(activity1.this,activity2.class); ...
- 不同Activity之间传递数据--Bundle对象和startActivityForResult方法的实现
首先,由于Activity是Android四大组件之一,如果一个应用程序中包含不止一个Activity,则需要在AndroidManifest.xml文件中进行声明. 例如进行如下的声明(程序中包含两 ...
- Oracle 21C 新特性:数据泵相关新特性汇总
墨墨导读:本文来自墨天轮用户"JiekeXu"投稿,墨天轮主页:https://www.modb.pro/u/434,本文分享Oracle 21c 新特性:数据泵相关新特性汇总. ...
最新文章
- Python中的变量以及赋值语句
- Logback 专题
- 360 php offer,审批终于通过了,从面试到拿到奇虎360的offer已经失…
- messageformat.format() 自定义参数名_DedeCMS的Java版mcms 第四季之一: 自定义插件
- java 文件名 类名_java文件名为什么要与类名相同
- mysql与sqlyog连接_如何用sqlyog实现远程连接mysql
- 【HDU6286】2018(容斥)
- 怎么使用7zip进行分批压缩_7zip怎么使用 7zip使用方法教程
- 保障系统迁移服务器搬迁,整体中心机房搬迁服务器idc数据迁移方案应用场景
- 三角波的傅里叶变换公式_南瓜老师的数学思维训练营 第14期 —— 三角恒等变换公式...
- 2021年的学习Flag:只争朝夕,不负韶华
- 快递系统java实验报告_java模拟物流快递系统程序.doc
- java获取本月最后一天
- Matlab绘制Sigmod、Tanh、ReLU
- 总结Python设置Excel单元格样式的一切,比官方文档还详细
- 最怕你不甘平庸,却又不去行动!
- Chap.6 总结《CL: An Introduction》 (Vyvyan Evans)
- 下载python后怎么运行代码,怎样下载python的编译器
- 电脑C盘无缘无故就占满了
- 那些年,我们在?的那些日子
热门文章
- matlab填充点面,求大神指点绘制空间内散点图的包络面,,,散点程序如下
- apache缓存清理_深挖 Mybatis 源码:缓存模块
- 【剑指offer - C++/Java】8、跳台阶
- 《linux就该这么学》第六节,计划任务和用户身份管理!
- 20154319 《网络对抗技术》后门原理与实践
- 使用SqlCommandBuilder
- NetTiers模板中各个选项的一些解释
- [密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第3篇]影响计算能力和存储能力的因素
- [Leedcode][JAVA][第105题][从前序与中序遍历序列构造二叉树][栈][递归][二叉树]
- 工厂方法模式_1天1个设计模式——工厂方法模式