[译][Google工程师] 刚刚发布了 Fragment 的新特性 “Fragment 间传递数据的新方式” 以及源码分析

简介: [译][Google工程师] 刚刚发布了 Fragment 的新特性 “Fragment 间传递数据的新方式” 以及源码分析

image.png



就在 2020/05/07 号 Now in Android #17 更新了,发布 Android 的新特性,其中就包括 Fragment 间通信的新方式,大家可以点击这里前往,看看都有那些更新


通过这篇文章你将学习到以下内容,将在译者思考部分会给出相应的答案


  • 新 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() 之前被调用)。


image.png


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


image.png


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


image.png


在 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 listener
    bundleOf(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 manager
    scenario.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 UI
   onView(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 listener
    var receivedData = ""
    scenario.onFragment { fragment ->
        fragment.parentFragmentManager.setFragmentResultListener(
            KEY,
            fragment,
            FragmentResultListener { key, result ->
                receivedData = result.getString(KEY_DATA)
            })
    }
    // Send data
    onView(withId(R.id.send_data)).perform(click())
    // Verify data was successfully sent
    assertThat(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 listener
    bundleOf(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 对应的 listener
    if (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() {
        @Override
        public 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 对应的 Bundle
        mResults.remove(requestKey);
        return;
    }
    // Check if there is a listener waiting for a result with this key
    // mResultListeners 是 ConcurrentHashMap 的实例,用来储存注册的 listener
    // 获取 requestKey 对应的 listener
    LifecycleAwareResultListener 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 之间通信最好的方式是什么?


参考文献




结语



致力于分享一系列 Android 系统源码、逆向分析、算法、翻译相关的文章,目前正在翻译一系列欧美精选文章,不仅仅是翻译,还有翻译背后对每篇文章思考,如果你喜欢这片文章,请帮我点个赞,感谢,期待与你一起成长


计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,目前已经包含了 App Startup、Paging3、Hilt 等等,正在逐渐增加其他 Jetpack 新成员,仓库持续更新,可以前去查看:AndroidX-Jetpack-Practice, 如果这个仓库对你有帮助,请帮我点个赞,我会陆续完成更多 Jetpack 新成员的项目实践。


算法


由于 LeetCode 的题库庞大,每个分类都能筛选出数百道题,由于每个人的精力有限,不可能刷完所有题目,因此我按照经典类型题目去分类、和题目的难易程度去排序


  • 数据结构: 数组、栈、队列、字符串、链表、树……
  • 算法: 查找算法、搜索算法、位运算、排序、数学、……


每道题目都会用 Java 和 kotlin 去实现,并且每道题目都有解题思路,如果你同我一样喜欢算法、LeetCode,可以关注我 GitHub 上的 LeetCode 题解:Leetcode-Solutions-with-Java-And-Kotlin,一起来学习,期待与你一起成长


Android 10 源码系列


正在写一系列的 Android 10 源码分析的文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,如果你同我一样喜欢研究 Android 源码,可以前往我 GitHub 上的 Android10-Source-Analysis,文章都会同步到这个仓库



工具系列



逆向系列




目录
相关文章
|
7月前
|
数据可视化 定位技术 Sentinel
如何用Google Earth Engine快速、大量下载遥感影像数据?
【2月更文挑战第9天】本文介绍在谷歌地球引擎(Google Earth Engine,GEE)中,批量下载指定时间范围、空间范围的遥感影像数据(包括Landsat、Sentinel等)的方法~
2697 1
如何用Google Earth Engine快速、大量下载遥感影像数据?
|
7月前
Google Earth Engine(GEE)——计算两列数据的相关性
Google Earth Engine(GEE)——计算两列数据的相关性
138 0
|
7月前
|
知识图谱
Google Earth Engine(GEE)——NASA NEX GDPDDP CMIP5数据集中的问题
Google Earth Engine(GEE)——NASA NEX GDPDDP CMIP5数据集中的问题
74 1
|
7月前
|
传感器 编解码 数据处理
Open Google Earth Engine(OEEL)——哨兵1号数据的黑边去除功能附链接和代码
Open Google Earth Engine(OEEL)——哨兵1号数据的黑边去除功能附链接和代码
148 0
|
7月前
|
计算机视觉
Google Earth Engine(GEE)——使用MODIS数据单点测试SG滤波和harmonics method 滤波的差异分析
Google Earth Engine(GEE)——使用MODIS数据单点测试SG滤波和harmonics method 滤波的差异分析
270 0
|
7月前
|
存储 数据可视化 定位技术
Google Earth Engine谷歌地球引擎GEE中ee.Image格式单张栅格图像数据基本处理操作
Google Earth Engine谷歌地球引擎GEE中ee.Image格式单张栅格图像数据基本处理操作
125 1
|
7月前
|
机器学习/深度学习 算法 数据可视化
基于Google Earth Engine云平台构建的多源遥感数据森林地上生物量AGB估算模型含生物量模型应用APP
基于Google Earth Engine云平台构建的多源遥感数据森林地上生物量AGB估算模型含生物量模型应用APP
253 0
|
7月前
|
数据可视化 数据挖掘 数据建模
R语言指数平滑法holt-winters分析谷歌Google Analytics博客用户访问时间序列数据
R语言指数平滑法holt-winters分析谷歌Google Analytics博客用户访问时间序列数据
|
7月前
|
传感器 移动开发 NoSQL
Google Earth Engine(GEE)——明尼苏达大学官方全球核南极洲DEM数据下载
Google Earth Engine(GEE)——明尼苏达大学官方全球核南极洲DEM数据下载
106 0
Google Earth Engine(GEE)——明尼苏达大学官方全球核南极洲DEM数据下载
|
7月前
|
编解码 定位技术 Python
Google Earth Engine谷歌地球引擎GEE批量下载ImageCollection遥感影像数据合集的方法
Google Earth Engine谷歌地球引擎GEE批量下载ImageCollection遥感影像数据合集的方法
479 2