Fragment 間通信的新方式,這些你是否又懂了呢?

1、概述

就在 2020/05/07 號 Now in Android #17 更新了,發(fā)布 Android 的新特性,其中就包括 Fragment 間通信的新方式

通過這篇文章你將學習到以下內容,將在后文部分會給出相應的答案
一、新 Fragment 間通信的方式的使用?
二、新 Fragment 間通信的源碼分析?
三、匯總 Fragment 之間的通信的方式?

2、在 Fragment 之間傳遞數據

Fragment 間傳遞數據可以通過多種方式,包括使用

一、target Fragment APIs (Fragment.setTargetFragment() Fragment.getTargetFragment())
二、ViewModel
三、Fragments’ 父容器 Activity

target Fragment APIs 已經過時了,現在鼓勵使用新的 Fragment result APIs 完成 Fragment 之間傳遞數據,其中傳遞數據由 FragmentManager 處理,并且在 Fragments 設置發(fā)送數據和接受數據。

使用新的 Fragment APIs 在 兩個 Fragment 之間的傳遞,沒有任何引用,可以使用它們公共的 FragmentManager,它充當 Fragment 之間傳遞數據的中心存儲。

接受數據

如果想在 Fragment 中接受數據,可以在 FragmentManager 中注冊一個 FragmentResultListener,參數 requestKey 可以過濾掉 FragmentManager 發(fā)送的數據

FragmentManager.setFragmentResultListener(
    requestKey,
    lifecycleOwner,
    FragmentResultListener { requestKey: String, result: Bundle ->
        // Handle result
    })

參數 lifecycleOwner 可以觀察生命周期,當 Fragment 的生命周期處于 STARTED 時接受數據。

如果監(jiān)聽 Fragment 的生命周期,您可以在接收到新數據時安全地更新 UI,因為 view 的創(chuàng)建(onViewCreated() 方法在 onStart() 之前被調用)。



當生命周期處于 LifecycleOwner STARTED 的狀態(tài)之前,如果有多個數據傳遞,只會接收到最新的值:



當生命周期處于 LifecycleOwner DESTROYED 時,它將自動移除 listener,如果想手動移除 listener,需要調用 FragmentManager.setFragmentResultListener() 方法,傳遞空的 FragmentResultListener

在 FragmentManager 中注冊 listener,依賴于 Fragment 發(fā)送返回的數據。

如果在 FragmentA 中接受 FragmentB 發(fā)送的數據,FragmentA 和 FragmentB 處于相同的層級,通過 parent FragmentManager 進行通信,FragmentA 必須使用 parent FragmentManager 注冊 listener

parentFragmentManager.setFragmentResultListener(...)

如果在 FragmentA 中接受 FragmentB 發(fā)送的數據,FragmentA 是 FragmentB 的父容器, 他們通過 child FragmentManager 進行通信

childFragmentManager.setFragmentResultListener(...)

listener 必須設置的Fragment 相同的 FragmentManager。

發(fā)送數據

如果 FragmentB 發(fā)送數據給 FragmentA,需要在 FragmentA 中注冊 listener,通過 parent FragmentManager 發(fā)送數據

parentFragmentManager.setFragmentResult(
    requestKey, // Same request key FragmentA used to register its listener
    bundleOf(key to value) // The data to be passed to FragmentA
)

3、測試 Fragment Results

測試 Fragment 是否成功接收或發(fā)送數據,可以使用 FragmentScenario API
接受數據

如果在 FragmentA 中注冊 FragmentResultListener 接受數據,你可以模擬 parent FragmentManager 發(fā)送數據,如果在 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"))) 
}

發(fā)送數據

可以在 FragmentB 的 parent FragmentManager 上注冊一個 FragmentResultListener 來測試 FragmentB 是否成功發(fā)送數據,當發(fā)送數據結束時,可以來驗證這個 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 處于不可預知狀態(tài)的時,可能發(fā)生未知的問題。

4、譯者的思考

這是譯者的一些思考,總結一下 Fragment 1.3.0-alpha04 新增加的 Fragment 間通信的 API

數據接受

FragmentManager.setFragmentResultListener(
    requestKey,
    lifecycleOwner,
    FragmentResultListener { requestKey: String, result: Bundle ->
        // Handle result
    })

數據發(fā)送

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 處于不可預知狀態(tài)的時,可能發(fā)生未知的問題
三、當生命周期處于 ON_DESTROY 時,移除監(jiān)聽

我們一起來從源碼的角度分析一下 Google 是如何做的。

5、源碼分析

按照慣例從調用的方法來分析,數據接受時,調用了 FragmentManager 的 setFragmentResultListener 方法

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 處于不可預知狀態(tài)的時,可能發(fā)生未知的問題
    if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
        return;
    }

    // 開始監(jiān)聽生命周期
    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 時,移除監(jiān)聽
            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 去監(jiān)聽 Fragment 的生命周期的變化
三、當生命周期處于 ON_START 時開始處理數據,避免當 Fragment 處于不可預知狀態(tài)的時,可能發(fā)生未知的問題
四、當生命周期處于 ON_DESTROY 時,移除監(jiān)聽

接下來一起來看一下數據發(fā)送的方法,調用了 FragmentManager 的 setFragmentResult 方法

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 狀態(tài)時,調用回調
        resultListener.onFragmentResult(requestKey, result);
    } else {
        // 否則保存當前傳輸的數據
        mResults.put(requestKey, result);
    }
}

一、獲取 requestKey 注冊的 listener
二、當生命周期處于 STARTED 狀態(tài)時,開始發(fā)送數據
三、否則保存當前傳輸的數據

源碼分析到這里結束了,我們一起來思考一下,在之前我們的都有那些數據傳方式。

匯總 Fragment 之間的通信的方式:

  1. 通過共享 ViewModel 或者關聯(lián) Activity來完成,Fragment 之間不應該直接通信

  2. 通過接口,可以在 Fragment 定義接口,并在 Activity 實現它

  3. 通過使用 findFragmentById 方法,獲取 Fragment 的實例,然后調用 Fragment 的公共方法

  4. 調用 Fragment.setTargetFragment() 和 Fragment.getTargetFragment() 方法,但是注意 target fragment 需要直接訪問另一個 fragment 的實例,這是十分危險的,因為你不知道目標 fragment 處于什么狀態(tài)

  5. Fragment 新的 API, setFragmentResult() 和 setFragmentResultListener()

綜合以上通信方式,那么你認為 Fragment 之間通信最好的方式是什么?

這里有我整理出來的一些資料,想要了解更多進階技術并獲取資料可以點擊我的github

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容