activity 点击后传递数据给fragment_Fragment 新特性 : Fragment Result API 使用以及源码分析

v2-2a311d637b44a3c886a5e5c9fe21f8cd_1440w.jpg?source=172ae18b
  • 原标题: Android Fragments: Fragment Result
  • 原文地址: https://proandroiddev.com/android-fragments-fragment-result......
  • 原文作者: Husayn Hakeem

今年 Google 推出了 Fragment Result APIActivity 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() 之前被调用)。

v2-bf93d82d95796419183ee206ddd09ba1_b.jpg

当生命周期处于 LifecycleOwner STARTED 的状态之前,如果有多个数据传递,只会接收到最新的值

v2-039cccc4999fbb57d7144a966f91633e_b.jpg

当生命周期处于 LifecycleOwner DESTROYED 时,它将自动移除 listener,如果想手动移除 listener,需要调用 FragmentManager.setFragmentResultListener() 方法,传递空的 FragmentResultListener

v2-10c9b67a48e49a2f8b47e3ff71388e30_b.jpg

在 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-Practice​github.com
  • LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析

v2-e30c2131d7ec03cb2464c922cea85b78_b.jpg
    • 剑指 offer 及国内外大厂面试题解:
剑指Offer题解​offer.hi-dhl.com
    • LeetCode 系列题解:
LeetCode 系列题解​leetcode.hi-dhl.com
  • 最新 Android 10 源码分析系列文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,仓库持续更新,欢迎前去查看
hi-dhl/Android10-Source-Analysis​github.com
v2-2d7236693ebdd47ea746af088e6f0124_ipico.jpg
  • 整理和翻译一系列精选国外的技术文章,每篇文章都会有译者思考部分,对原文的更加深入的解读,仓库持续更新,欢迎前去查看
hi-dhl/Technical-Article-Translation​github.com
v2-2d7236693ebdd47ea746af088e6f0124_ipico.jpg
  • 「为互联网人而设计,国内国外名站导航」涵括新闻、体育、生活、娱乐、设计、产品、运营、前端开发、Android 开发等等网址,欢迎前去查看
Hi World | 为互联网人而设计的国内国外名站导航​site.51git.cn

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

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

相关文章

linux动态库与静态库混合连接

在应用程序需要连接外部库的情况下&#xff0c;linux默认对库的连接是使用动态库&#xff0c;在找不到动态库的情况下再选择静态库。使用方式为&#xff1a; gcc test.cpp -L. -ltestlib 如果当前目录有两个库libtestlib.so libtestlib.a 则肯定是连接libtestlib.so。如果要指定…

java运算函数_Java中的数学计算函数

Math类&#xff1a;java.lang.Math类中包含基本的数字操作&#xff0c;如指数、对数、平方根和三角函数。java.math是一个包&#xff0c;提供用于执行任意精度整数(BigInteger)算法和任意精度小数(BigDecimal)算法的类。java.lang.Math类中包含E和PI两个静态常量&#xff0c;以…

从无头单链表中删除节点

#include<iostream> using namespace std;struct Node{int data;Node* next; };void deleteNode(Node* p) {p->datap->next->data; //将p后的节点的数值赋给p的data域p->nextp->next->next; //将p后的节点删除&#xff0c;即完成了删除节点p的效果…

android卡片层叠效果_ReactNative之Android绝对布局position:#x27;absolute#x27;问题

工作中会遇到各种各样的问题&#xff0c;ReactNative开发也是填坑不止。比如最近在开发需求中&#xff0c;就遇到一个问题。在一个卡片类型的右上角添加一个删除按钮。使用了绝对布局position:absolute属性&#xff0c;在Android上却无法正常显示&#xff0c;很是烦恼。有一个相…

java监听机制_详解java的事件监听机制和观察者设计模式

首先说说监听器&#xff1a;监听器就是一个实现特定接口的普通java程序&#xff0c;这个程序专门用于监听另一个java对象的方法调用或属性改变&#xff0c;当被监听对象发生上述事件后&#xff0c;监听器某个方法将立即被执 行。java的事件监听机制可概括为3点&#xff1a;1,ja…

敏捷开发的6个实战经验

在大型企业中经常是各种软件开发模式混用&#xff0c;一些采用敏捷开发&#xff0c;一些则是采用传统的瀑布式或RUP&#xff08;统一软件开发过程&#xff09;。敏捷开发&#xff0c;相对传统软件开发模式&#xff0c;它主要是针对快速变化的需求&#xff0c;不断优化管理流程&…

C++ Editbox换行问题

在EditBox中"/r/n" 换行有时是不行的。 使用&#xff1a;strFilePath.Format("File Name: %s%c%c", strFileName,0x0D,0x0A); //strFilePath.Format("File Name: %s/r/n", strFileName);转载于:https://www.cnblogs.com/mygod/archive/2012/11/1…

java 正则匹配括号是否成对_十分钟学会正则表达式

正则表达式用处挺广的&#xff0c;主要用于处理字符串。正则引擎想要在计算机语言中使用正则表达式&#xff0c;那么这门计算机语言必须要利用正则引擎去实现相应的正则库。主要的正则引擎分为以下两类&#xff1a;DFA 确定性的状态机。不使用”回溯”&#xff0c;效率高&#…

shell 脚本比较字符串相等_比较带空格的字符串相等的Shell脚本 如何比较两个.....

****************************比较带空格的字符串相等的Shell脚本***************************如果要比较的字符串中间有空格&#xff0c;可以用下面的程序(摘自《Unix Shells by Example, 3rd Edition》)name"Joe Blow"if [[ $name "Joe Blow" ]]thenpri…

Linux 下编译并安装配置 Qt

本文介绍的是Linux 下编译并安装配置 Qt&#xff0c;最近准备做 Nokia 的 Symbian,Maemo 下触摸屏开发。考虑到程序的跨平台可移植性&#xff0c;最终选择使用 Qt 开发。相对来说&#xff0c;国内关于 Qt 相关文档并不算很多。作者将 Linux 下编译并安装配置 Qt 全过程总结了…

itext jsp页面打印

最近项目中需要一个打印功能&#xff0c;需求很简单&#xff0c;只要打印出单据就可以了&#xff0c;画出一个表格&#xff0c;一些信息需要从数据库中提取 找到了免费的itext&#xff0c;可以实现我的简单的功能了 代码&#xff1a;&#xff08;打印方法&#xff09; public S…

android token机制_你真的了解16.6ms刷新机制吗?

阅读本文前&#xff0c;请您先点击上面的蓝色字体“Android扫地僧”&#xff0c;“关注”后再点击置顶公众号&#xff0c;优质干货&#xff0c;重磅资源第一时间送达。散人丶https://juejin.im/post/5ce686a46fb9a07ec754f470前言之前在整理知识的时候&#xff0c;看到android屏…

dfa2.java 原理_DFA编程练习2

题目: 请设计DFA, 使其接受全部含有奇数个1的串, 假定 ∑ {0, 1}.解:DFA可能出现两个个状态:qeven: 读入了偶数个1的串.qodd: 读入了奇数个1的串, 该状态也是终结状态(accept state).它们的状态转移图如下:编写程序, 运行效果如下:测试用例说明:0000不被上图的DFA接受1111不被…

Asp.net的HTTP请求处理过程

说明&#xff1a; &#xff08;1&#xff09;、客户端浏览器向服务器发出一个Http请求&#xff0c;此请求会被inetinfo.exe进程截获&#xff0c;然后转交给 aspnet_isapi.dll进程&#xff0c;接着它又通过Http Pipeline的管道&#xff0c;传送给aspnet_wp.exe这个进程&#xff…

ubuntu13.10 编译时 关于链接xlib 库阶段出错的问题解决

/usr/bin/ld: fmouse_main.o: undefined reference to symbol XFlush /usr/lib/i386-linux-gnu/libX11.so.6: error adding symbols: DSO missing from command line collect2: error: ld returned 1 exit status make: *** [freepen_drv] 错误 1 以上为错误提示&#xff0c…

fread读取整个文件_qt如何实现大文件的加载和显示

最近研究了下如何用qt的原生控件来加载和显示大文件&#xff08;>1G&#xff09;&#xff0c;分享下一些摸索经验。下文源码&#xff1a;compilelife/loginsight​github.com文件的内存映射在开始qt部分之前&#xff0c;我们先了解一个概念——文件的内存映射。我们知道一般…

[转]listview中设置背景图片后 拉动变黑

经本人亲测有效...在Android中&#xff0c;ListView是最常用的一个控件&#xff0c;在做UI设计的时候&#xff0c;很多人希望能够改变一下它的背景&#xff0c;使他能够符合整体的UI设计&#xff0c;改变背景背很简单只需要准备一张图片然后指定属性 android:background"d…

编译pjsip2.1.0 vidgui程序时,xlib保错问题

/usr/lib/i386-linux-gnu/libX11.so.6: error adding symbols: DSO missing from command line 以上为错误提示&#xff0c;为链接阶段未指定正确的库文件导致&#xff1b; 解决办法&#xff1a; 修改pro文件&#xff0c;增加下面这一行 LIB -lX11 -L/usr/lib/i386-linux-g…

linux内核编译及添加系统调用(hdu)_浅谈关于Linux内核write系统调用操作的原子性

Linux系统的write调用到底是不是原子的。网上能搜出一大堆文章&#xff0c;基本上要么是翻译一些文献&#xff0c;要么就是胡扯&#xff0c;本文中我来结合实例来试着做一个稍微好一点的回答。先摆出结论吧。结论包含两点&#xff0c;即write调用不能保证什么以及write调用能保…

java 判断对象为控制_Java流程控制

Java流程控制1、Scanner对象①java.util.Scanner是Java5的新特性&#xff0c;可以通过Scanner类来获取用户的输入。②基本语法&#xff1a;1 Scanner snew Scanner(System.in);③通过next()和nextLine()方法接受用户输入&#xff0c;通过hasNext()和hasNextLine()方法来判断用户…