一道面试题:介绍一下 Fragment 间的通信方式?

Fragment 间的通信可以借助以下几种方式实现:

  1. EventBus
  2. Activity(or Parent Fragment)
  3. ViewModel
  4. Result API

1. 基于 EventBus 通信

EventBus 的优缺点都很突出。 优点是限制少可随意使用,缺点是限制太少使用太随意。

因为 EventBus 会导致开发者在架构设计上“不思进取”,随着项目变复杂,结构越来越混乱,代码可读性变差,数据流的变化难以追踪。

所以,规模越大的项目 EvenBus 的负面效果越明显,因此很多大厂都禁止 EventBus 的使用。所以这道题千万不要把 EventBus 作为首选答案,比较得体的回答是:

“ EventBus 具备通信能力,但是缺点很突出,大量使用 EventBus 会造成项目难以维护、问题难以定位,所以我不建议在项目中使用 EventBus 进行通信。 ”

2. 基于 Activity 或父 Fragment 通信

为了迭代更加敏捷,Fragment 从 AOSP 迁移到了 AndroidX ,这导致同时存在着两种包名的 Fragment:android.app.Fragmentandoridx.fragment.app.Fragment

虽然前者已经被废弃,但很多历史代码中尚存, 对于老的Fragment,经常依赖基于 Activity 的通信方式,因为其他通信方式大都依赖 AndroidX 。

class MainActivity : AppCompatActivity() {val listFragment: ListFragment by lazy {ListFragment()}val CreatorFragment: CreatorFragment by lazy {// 构建Fragment的时候设置 Callback,建立通信CreatorFragment().apply { setOnItemCreated { listFragment.addItem(it)}}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)supportFragmentManager.beginTransaction().apply {add(R.id.fragmentContainer, listFragment)commit()}}
}

如上,在 Activity 或父 Fragment 中创建子Fragment,同时为其设置 Callback

此时,Fragment 的创建依赖手动配置,无法在 ConfigurationChangeed 的时候自动恢复重建,所以除了用来处理 android.app.Fragment 的历史遗留代码之外,不推荐使用。

3. 基于 ViewModel 通信

ViewModel 是目前使用最广泛的通信方式之一,在 Kotlin 中使用时,需要引入fragment-ktx

class ListViewModel : ViewModel() {private val originalList: LiveData<List<Item>>() = ...val itemList: LiveData<List<Item>> = ...fun addItem(item: Item) { //更新 LiveData}}class ListFragment : Fragment() {// 借助ktx,使用activityViewModels()代理方式获取ViewModelprivate val viewModel: ListViewModel by activityViewModels()override fun onViewCreated(view: View, savedInstanceState: Bundle?) {viewModel.itemList.observe(viewLifecycleOwner, Observer { list ->// Update the list UI}}
}class CreatorFragment : Fragment() {private val viewModel: ListViewModel by activityViewModels()override fun onViewCreated(view: View, savedInstanceState: Bundle?) {button.setOnClickListener {val item = ...viewModel.addItem(item)}}
}

如上,通过订阅 ViewModel 的 LiveData,接受数据变通的通知。因为两个 Fragment 需要共享ViewModel,所以 ViewModel 必须在 Activity 的 Scope 中创建

关于 ViewModel 的实现原理,相关文章很多,本文不做赘述了。接下来重点看一下 Result API:

4. 基于 Resutl API 通信

Fragment 1.3.0-alpha04起,FragmentManager 新增了 FragmentResultOwner接口,顾名思义 FragmentManager 成为了 FragmentResult 的持有者,可以进行 Fragment 之间的通信。

假设需要在 FragmentA 监听 FragmentB 返回的数据,首先在 FragmentA 设置监听

override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// setFragmentResultListener 是 fragment-ktx 提供的扩展函数setFragmentResultListener("requestKey") { requestKey, bundle ->// 监听key为“requestKey”的结果, 并通过bundle获取val result = bundle.getString("bundleKey")// ...}
}// setFragmentResultListener 是Fragment的扩展函数,内部调用 FragmentManger 的同名方法
public fun Fragment.setFragmentResultListener(requestKey: String,listener: ((requestKey: String, bundle: Bundle) -> Unit)
) {parentFragmentManager.setFragmentResultListener(requestKey, this, listener)
}

当从 FragmentB 返回结果时:

button.setOnClickListener {val result = "result"setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}//setFragmentResult 也是 Fragment 的扩展函数,其内部调用 FragmentManger 的同名方法
public fun Fragment.setFragmentResult(requestKey: String, result: Bundle) {parentFragmentManager.setFragmentResult(requestKey, result)
}

上面的代码可以用下图表示:

Result API的原理非常简单,FragmentA 通过 Key 向 FragmentManager 注册 ResultListener,FragmentB 返回 result 时, FM 通过 Key 将结果回调给FragmentA 。需要特别注意的是只有当 FragmentB 返回时,result才会被真正回传,如果 setFragmentResult 多次,则只会保留最后一次结果。

生命周期可感知

通过梳理源码可以知道Result API是LifecycleAware的

源码基于 androidx.fragment:fragment:1.3.0

setFragmentResultListener 实现:

//FragmentManager.java
private final Map<String, LifecycleAwareResultListener> mResultListeners =Collections.synchronizedMap(new HashMap<String, LifecycleAwareResultListener>());public final void setFragmentResultListener(@NonNull final String requestKey,@NonNull final LifecycleOwner lifecycleOwner,@NonNull final FragmentResultListener listener) {final Lifecycle lifecycle = lifecycleOwner.getLifecycle();LifecycleEventObserver observer = new LifecycleEventObserver() {if (event == Lifecycle.Event.ON_START) {// once we are started, check for any stored resultsBundle storedResult = mResults.get(requestKey);if (storedResult != null) {// if there is a result, fire the callbacklistener.onFragmentResult(requestKey, storedResult);// and clear the resultclearFragmentResult(requestKey);}}if (event == Lifecycle.Event.ON_DESTROY) {lifecycle.removeObserver(this);mResultListeners.remove(requestKey);}};lifecycle.addObserver(observer);LifecycleAwareResultListener storedListener = mResultListeners.put(requestKey,new LifecycleAwareResultListener(lifecycle, listener, observer));if (storedListener != null) {storedListener.removeObserver();}}
  • listener.onFragmentResultLifecycle.Event.ON_START 的时候才调用,也就是说只有当 FragmentA 返回到前台时,才会收到结果,这与 LiveData 的逻辑的行为一致,都是 LifecycleAware 的

  • 当多次调用 setFragmentResultListener 时, 会创建新的 LifecycleEventObserver 对象, 同时旧的 observer 会随着 storedListener.removeObserver() 从 lifecycle 中移除,不能再被回调。

也就是说,对于同一个 requestKey 来说,只有最后一次设置的 listener 有效,这好像也是理所应当的,毕竟不叫 addFragmentResultListener

setFragmentResult 实现:

 private final Map<String, Bundle> mResults =Collections.synchronizedMap(new HashMap<String, Bundle>());public final void setFragmentResult(@NonNull String requestKey, @NonNull Bundle result) {// Check if there is a listener waiting for a result with this keyLifecycleAwareResultListener resultListener = mResultListeners.get(requestKey);// if there is and it is started, fire the callbackif (resultListener != null && resultListener.isAtLeast(Lifecycle.State.STARTED)) {resultListener.onFragmentResult(requestKey, result);} else {// else, save the result for latermResults.put(requestKey, result);}}

setFragmentResult 非常简单, 如果当前是 listener 处于前台,则立即回调 setFragmentResult(), 否则,存入 mResults, 等待 listener 切换到前台时再回调。

一个 listener 为什么有前台/后台的概念呢,这就是之前看到的 LifecycleAwareResultListener 了, 生命周期可感知是因为其内部持有一个 Lifecycle, 而这个 Lifecycle 其实就是设置 listener 的那个 Fragment

 private static class LifecycleAwareResultListener implements FragmentResultListener {private final Lifecycle mLifecycle;private final FragmentResultListener mListener;private final LifecycleEventObserver mObserver;LifecycleAwareResultListener(@NonNull Lifecycle lifecycle,@NonNull FragmentResultListener listener,@NonNull LifecycleEventObserver observer) {mLifecycle = lifecycle;mListener = listener;mObserver = observer;}public boolean isAtLeast(Lifecycle.State state) {return mLifecycle.getCurrentState().isAtLeast(state);}@Overridepublic void onFragmentResult(@NonNull String requestKey, @NonNull Bundle result) {mListener.onFragmentResult(requestKey, result);}public void removeObserver() {mLifecycle.removeObserver(mObserver);}}

可恢复重建

mResult 中的数据是会随着 Fragment 的重建可以恢复的,所以 FragmentA 永远不会丢失 FragmentB 返回的结果。当然,一旦 Result 被消费,就会从 mResult 中清除

mResults 的保存

//FragmentManager.java
void restoreSaveState(@Nullable Parcelable state) {//...ArrayList<String> savedResultKeys = fms.mResultKeys;if (savedResultKeys != null) {for (int i = 0; i < savedResultKeys.size(); i++) {mResults.put(savedResultKeys.get(i), fms.mResults.get(i));}}
}

mResults 的恢复

Parcelable saveAllState() {// FragmentManagerState implements ParcelableFragmentManagerState fms = new FragmentManagerState();//...fms.mResultKeys.addAll(mResults.keySet());fms.mResults.addAll(mResults.values());//...return fms;
}

如何选择?Result API 与 ViewModel

ResultAPI 与 ViewModel + LiveData 有一定相似性,都是生命周期可感知的,都可以在恢复重建时保存数据,那这两种通信方式该如何选择呢?

对此,官方给的建议如下:

The Fragment library provides two options for communication: a shared ViewModel and the Fragment Result API. The recommended option depends on the use case. To share persistent data with any custom APIs, you should use a ViewModel. For a one-time result with data that can be placed in a Bundle, you should use the Fragment Result API.

  • ResultAPI 主要适用于那些一次性的通信场景(FragmentB返回结果后结束自己)。如果使用 ViewModel,需要上提到的 Fragment 共同的父级 Scope,而 Scope 的放大不利于数据的管理。

  • 非一次性的通信场景,由于 FragmentA 和 FragmentB 在通信过程中共存,推荐通过共享 ViewModel 的方式,再借助 LiveData 等进行响应式通信。

5. 跨Activity的通信

最后看一下,跨越不同 Activity 的 Fragmnet 间的通信

跨 Activity 的通信主要有两种方式:

  • startActivityResult
  • Activity Result API

startActivityResult

Result API出现之前,需要通过 startActivityResult 完成通信,这也是 android.app.Fragment 唯一可选的方式。

通信过程如下:

  1. FragmentA 调用 startActivityForResult() 方法之后,跳转到 ActivityB 中,ActivityB 把数据通过 setArguments() 设置给 FragmentB

  2. FragmentB 调用 getActivity().setResult() 设置返回数据,FragmentA 在 onActivityResult() 中拿到数据

此时,有两点需要特别注意:

  1. 不要使用 getActivity().startActivityForResult() , 而是在Fragment中直接调用startActivityForResult()

  2. activity 需要重写 onActivityResult,其必须调用 super.onActivityResult(requestCode, resultCode, data)

以上两点如果违反,则 onActivityResult 只能够传递到 activity 的,无法传递到 Fragment

Result API

1.3.0-alpha02起,Fragment 支持 registerForActivityResult() 的使用,通过 Activity 的 ResultAPI 实现跨 Activity 通信。

FragmentA 设置回调:

class FragmentA : Fragment() {private val startActivityLauncher: ActivityResultLauncher<Intent> =registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {if (it.resultCode == Activity.RESULT_OK) {//} else if (it.resultCode == Activity.RESULT_CANCELED) {//}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)startActivityLauncher.launch(Intent(requireContext(), ActivityB::class.java))}
}

FragmentB 返回结果

button.setOnClickListener {val result = "result"// Use the Kotlin extension in the fragment-ktx artifactsetFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

了解 Activity Result API 的同学对上述过程应该很熟悉。

简单看一下源码。

源码基于 androidx.fragment:fragment:1.3.0

我们在 FragmentA 中通过创建一个 ActivityResultLauncher,然后调用 launch 启动目标 ActivityB

//Fragment # prepareCallInternalreturn new ActivityResultLauncher<I>() {@Overridepublic void launch(I input, @Nullable ActivityOptionsCompat options) {ActivityResultLauncher<I> delegate = ref.get();if (delegate == null) {throw new IllegalStateException("Operation cannot be started before fragment "+ "is in created state");}delegate.launch(input, options);}//...};

可以看到,内部调用了delegate.launch, 我们追溯一下 delegate 的出处,即 ref 中设置的 value

//Fragment # prepareCallInternalregisterOnPreAttachListener(new OnPreAttachedListener() {@Overridevoid onPreAttached() {//ref中注册了一个launcher,来自 registryProvider 提供的 ActivityResultRegistryfinal String key = generateActivityResultKey();ActivityResultRegistry registry = registryProvider.apply(null);ref.set(registry.register(key, Fragment.this, contract, callback));}});public final <I, O> ActivityResultLauncher<I> registerForActivityResult(@NonNull final ActivityResultContract<I, O> contract,@NonNull final ActivityResultCallback<O> callback) {return prepareCallInternal(contract, new Function<Void, ActivityResultRegistry>() {@Overridepublic ActivityResultRegistry apply(Void input) {//registryProvider 提供的 ActivityResultRegistry 来自 Activityif (mHost instanceof ActivityResultRegistryOwner) {return ((ActivityResultRegistryOwner) mHost).getActivityResultRegistry();}return requireActivity().getActivityResultRegistry();}}, callback);}

上面可以看到 ref 中设置的 ActivityResultLauncher 来自 Activity 的 ActivityResultRegistry ,也就说 Fragment 的 launch,最终是由其 mHost 的 Activity 代理的。

后续也就是 Activity 的 Result API 的流程了,我们知道 Activity Result API 本质上是基于 startActivityForResult 实现的,具体可以参考这篇文章,本文不再赘述了

总结

本文总结了 Fragment 通信的几种常见方式,着重分析了 Result API 实现原理。 fragment-1.3.0以后,对于一次性通信推荐使用 Result API 替代旧有的 startActivityForResult;响应式通信场景则推荐使用 ViewModel + LiveData (or StateFlow) , 尽量避免使用 EventBus 这类工具进行通信。

下面我针对Android 中不同的技术模块,整理了一些相关的 学习文档笔记 及相应的 面试习题(含答案),大家可以针对性的进行参考学习与复习。

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

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

相关文章

软路由ip的优势与劣势:了解其适用场景和限制

在网络技术的快速发展中&#xff0c;软路由IP作为一种灵活且功能强大的网络设备&#xff0c;越来越受到人们的关注。然而&#xff0c;正如任何技术一样&#xff0c;软路由IP也有其优势和劣势。本文将深入探讨软路由IP的优势、劣势以及其适用场景和限制&#xff0c;帮助你更好地…

深入讲解内存分配函数 malloc 原理及实现

任何一个用过或学过C的人对 malloc 都不会陌生。大家都知道malloc可以分配一段连续的内存空间&#xff0c;并且在不再使用时可以通过free释放掉。但是&#xff0c;许多程序员对malloc背后的事情并不熟悉&#xff0c;许多人甚至把malloc当做操作系统所提供的系统调用或C的关键字…

Streamlit 讲解专栏(十二):数据可视化-图表绘制详解(下)

文章目录 1 前言2 使用st.vega_lite_chart绘制Vega-Lite图表2.1 示例1&#xff1a;绘制散点图2.2 示例2&#xff1a;自定义主题样式 3 使用st.plotly_chart函数创建Plotly图表3.1 st.plotly_chart函数的基本用法3.2 st.plotly_chart 函数的更多用法 4 Streamlit 与 Bokeh 结合进…

运维Shell脚本小试牛刀(七):在函数文脚本件中调用另外一个脚本文件中函数|函数递归调用|函数后台执行

运维Shell脚本小试牛刀(一) 运维Shell脚本小试牛刀(二) 运维Shell脚本小试牛刀(三)::$(cd $(dirname $0)&#xff1b; pwd)命令详解 运维Shell脚本小试牛刀(四): 多层嵌套if...elif...elif....else fi_蜗牛杨哥的博客-CSDN博客 Cenos7安装小火车程序动画 运维Shell脚本小试…

React笔记(八)Redux

一、安装和配置 React 官方并没有提供对应的状态机插件&#xff0c;因此&#xff0c;我们需要下载第三方的状态机插件 —— Redux。 1、下载Redux 在终端中定位到项目根目录&#xff0c;然后执行以下命令下载 Redux npm i redux 2、创建配置文件 在 React 中&#xff0c;…

mysql创建用户

创建用户 创建 -- 创建用户 itcast , localhost只能够在当前主机localhost访问, 密码123456; create user test01localhost identified by 123456;使用命令show databases;命令&#xff0c;只显示一个数据库&#xff0c;因为没有权限 -- 创建用户 test02, 可以在任意主机访问…

Java基础(四)

151. LinkedList特征分析 增删快 可以打断连接&#xff0c;重新赋值引用&#xff0c;不 涉及数据移动操作,效率高 查询慢 双向链表结构数据存储非连 续&#xff0c;需要通过元素一一 跳转 152 ArrayList和LinkedList对比分析 ArrayList特征 查询快。增删慢 适用于数据产出之…

cookies 设置过期时间

1.如何在浏览器中查看cookie过期时间 F12-Application-Cookies可以查看到网页所有设置cookie值&#xff0c; 如果设置了过期时间的cookie是可以看到过期时间的持久cookie&#xff08;persistent cookie&#xff09;&#xff0c; 没有设置过期时间的是会话cookie&#xff08;s…

基于云计算的区域LIS系统系统源码

在医疗机构内部&#xff0c;院内实验室主要负责本院临床科室的检验&#xff0c;院内LIS系统必须满足实验室日常的标本处理入库、仪器联机、检验结果处理、报告打印、报告发布、检验信息统计、检验信息报告发布、标本流程、外部医疗机构检验报告调阅等工作。 在医疗机构间&#…

ARM编程模型-常用指令集

一、ARM指令集 ARM是RISC架构&#xff0c;所有的指令长度都是32位&#xff0c;并且大多数指令都在一个单周期内执行。主要特点&#xff1a;指令是条件执行的&#xff0c;内存访问使用Load/store架构。 二、Thumb 指令集 Thumb是一个16位的指令集&#xff0c;是ARM指令集的功能…

WEB APIs day6

一、正则表达式 RegExp是正则表达式的意思 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" co…

利用less实现多主题切换(配合天气现象)

1. 先看效果&#xff1a; 2. 话不多说直接撸吧&#xff1a; 原理&#xff1a;先给body元素添加style&#xff0c;再根据天气现象动态更改style 开撸&#xff1a; 创建src/assets/style/variables.less 使用 XXX:var(–XXX,‘style’) 声明系列变量&#xff0c;之后添加其他变…