一道面试题:介绍一下 Fragment 间的通信方式?
yuyutoo 2024-11-04 16:03 7 浏览 0 评论
Fragment 间的通信可以借助以下几种方式实现:
- EventBus
- Activity(or Parent Fragment)
- ViewModel
- Result API
1. 基于 EventBus 通信
EventBus 的优缺点都很突出。 优点是限制少可随意使用,缺点是限制太少使用太随意。
因为 EventBus 会导致开发者在架构设计上“不思进取”,随着项目变复杂,结构越来越混乱,代码可读性变差,数据流的变化难以追踪。
所以,规模越大的项目 EvenBus 的负面效果越明显,因此很多大厂都禁止 EventBus 的使用。所以这道题千万不要把 EventBus 作为首选答案,比较得体的回答是:
“ EventBus 具备通信能力,但是缺点很突出,大量使用 EventBus 会造成项目难以维护、问题难以定位,所以我不建议在项目中使用 EventBus 进行通信。 ”
2. 基于 Activity 或父 Fragment 通信
为了迭代更加敏捷,Fragment 从 AOSP 迁移到了 AndroidX ,这导致同时存在着两种包名的 Fragment:android.app.Fragment 和 andoridx.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()代理方式获取ViewModel
private 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 results
Bundle storedResult = mResults.get(requestKey);
if (storedResult != null) {
// if there is a result, fire the callback
listener.onFragmentResult(requestKey, storedResult);
// and clear the result
clearFragmentResult(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.onFragmentResult 在 Lifecycle.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 key
LifecycleAwareResultListener resultListener = mResultListeners.get(requestKey);
// if there is and it is started, fire the callback
if (resultListener != null && resultListener.isAtLeast(Lifecycle.State.STARTED)) {
resultListener.onFragmentResult(requestKey, result);
} else {
// else, save the result for later
mResults.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);
}
@Override
public 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 Parcelable
FragmentManagerState 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 唯一可选的方式。
通信过程如下:
- FragmentA 调用 startActivityForResult() 方法之后,跳转到 ActivityB 中,ActivityB 把数据通过 setArguments() 设置给 FragmentB
- FragmentB 调用 getActivity().setResult() 设置返回数据,FragmentA 在 onActivityResult() 中拿到数据
此时,有两点需要特别注意:
- 不要使用 getActivity().startActivityForResult() , 而是在Fragment中直接调用startActivityForResult()
- 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 artifact
setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}
了解 Activity Result API 的同学对上述过程应该很熟悉。
简单看一下源码。
源码基于 androidx.fragment:fragment:1.3.0
我们在 FragmentA 中通过创建一个 ActivityResultLauncher,然后调用 launch 启动目标 ActivityB
//Fragment # prepareCallInternal
return new ActivityResultLauncher<I>() {
@Override
public 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 # prepareCallInternal
registerOnPreAttachListener(new OnPreAttachedListener() {
@Override
void onPreAttached() {
//ref中注册了一个launcher,来自 registryProvider 提供的 ActivityResultRegistry
final 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>() {
@Override
public ActivityResultRegistry apply(Void input) {
//registryProvider 提供的 ActivityResultRegistry 来自 Activity
if (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 开发相关的学习文档、面试题,希望能帮助到大家学习提升,如有需要参考的可以直接私信“1”哦
相关推荐
- 全局和隐式 using 指令详解(全局命令)
-
1.什么是全局和隐式using?在.NET6及更高版本中,Microsoft引入了...
- 请停止微服务,做好单体的模块化才是王道:Spring Modulith介绍
-
1、介绍模块化单体是一种架构风格,代码是根据模块的概念构成的。对于许多组织而言,模块化单体可能是一个很好的选择。它有助于保持一定程度的独立性,这有助于我们在需要的时候轻松过渡到微服务架构。Spri...
- ASP.NET程序集引用之痛:版本冲突、依赖地狱等解析与实战
-
我是一位多年后端经验的工程师,其中前几年用ASP.NET...
- .NET AOT 详解(.net 6 aot)
-
简介AOT(Ahead-Of-TimeCompilation)是一种将代码直接编译为机器码的技术,与传统的...
- 一款基于Yii2开发的免费商城系统(一款基于yii2开发的免费商城系统是什么)
-
哈喽,我是老鱼,一名致力于在技术道路上的终身学习者、实践者、分享者!...
- asar归档解包(游戏arc文件解包)
-
要学习Electron逆向,首先要有一个Electron开发的程序的发布的包,这里就以其官方的electron-quick-start作为例子来进行一下逆向的过程。...
- 在PyCharm 中免费集成Amazon CodeWhisperer
-
CodeWhisperer是Amazon发布的一款免费的AI编程辅助小工具,可在你的集成开发环境(IDE)中生成实时单行或全函数代码建议,帮助你快速构建软件。简单来说,AmazonCodeWhi...
- 2014年最优秀JavaScript编辑器大盘点
-
1.WebstormWebStorm是一种轻量级的、功能强大的IDE,为Node.js复杂的客户端开发和服务器端开发提供完美的解决方案。WebStorm的智能代码编辑器支持JavaScript,...
- 基于springboot、tio、oauth2.0前端vuede 超轻量级聊天软件分享
-
项目简介:基于JS的超轻量级聊天软件。前端:vue、iview、electron实现的PC桌面版聊天程序,主要适用于私有云项目内部聊天,企业内部管理通讯等功能,主要通讯协议websocket。支持...
- JetBrains Toolbox推出全新产品订阅授权模式
-
捷克知名软件开发公司JetBrains最为人所熟知的产品是Java编程语言开发撰写时所用的集成开发环境IntelliJIDEA,相信很多开发者都有所了解。而近期自2015年11月2日起,JetBr...
- idea最新激活jetbrains-agent.jar包,亲测有效
-
这里分享一个2019.3.3版本的jetbrains-agent.jar,亲测有效,在网上找了很多都不能使用,终于找到一个可以使用的了,这里分享一下具体激活步骤,此方法适用于Jebrains家所有产品...
- CountDownTimer的理解(countdowntomars)
-
CountDownTimer是android开发常用的计时类,按照注释中的说明使用方法如下:kotlin:object:CountDownTimer(30000,1000){...
- 反射为什么性能会很慢?(反射时为什么会越来越长)
-
1.背景前段时间维护一个5、6年前的项目,项目总是在某些功能使用上不尽人意,性能上总是差一些,仔细过了一下代码发现使用了不少封装好的工具类,工具类里面用了好多的反射,反射会影响到执行效率吗?盲猜了一...
- btrace 开源!基于 Systrace 高性能 Trace 工具
-
介绍btrace(又名RheaTrace)是抖音基础技术团队自研的一款高性能AndroidTrace工具,它基于Systrace实现,并针对Systrace不足之处加以改进,核心改进...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- .NET 奇葩问题调试经历之3——使用了grpc通讯类库后,内存一直增长......
- 全局和隐式 using 指令详解(全局命令)
- 请停止微服务,做好单体的模块化才是王道:Spring Modulith介绍
- ASP.NET程序集引用之痛:版本冲突、依赖地狱等解析与实战
- .NET AOT 详解(.net 6 aot)
- 一款基于Yii2开发的免费商城系统(一款基于yii2开发的免费商城系统是什么)
- asar归档解包(游戏arc文件解包)
- 在PyCharm 中免费集成Amazon CodeWhisperer
- 2014年最优秀JavaScript编辑器大盘点
- 基于springboot、tio、oauth2.0前端vuede 超轻量级聊天软件分享
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)