美文网首页Android技术知识
简要的说一下:Fragment 间的通信方式?

简要的说一下:Fragment 间的通信方式?

作者: 搬砖小老弟 | 来源:发表于2022-06-29 21:25 被阅读0次

    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()代理方式获取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.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 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 唯一可选的方式。

    通信过程如下:

    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 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 开发相关的学习文档、面试题、Android 核心笔记等等文档,希望能帮助到大家学习提升,如有想参考小编 PDF学习文档的如果有需要的可以 点击这里直接获取!!!里面记录许多Android 相关学习知识点。

    相关文章

      网友评论

        本文标题:简要的说一下:Fragment 间的通信方式?

        本文链接:https://www.haomeiwen.com/subject/hxexbrtx.html