Fragment 的新特性: 「Fragment 間傳遞數據的新方式」 ,以及源碼分析!

做者:Husayn Hakeem,譯者:HiDhl, 連接:https://juejin.im/post/5eb58da05188256d6d6bb248java

就在 2020/05/07Now in Android #17 更新了,發佈 Android 的新特性,其中就包括 Fragment 間通訊的新方式,你們能夠點擊這裏前往,看看都有那些更新 經過這篇文章你將學習到如下內容,將在譯者思考部分會給出相應的答案android

  • 新 Fragment 間通訊的方式的使用?
  • 新 Fragment 間通訊的源碼分析?
  • 彙總 Fragment 之間的通訊的方式?

譯文

Frrgament 間傳遞數據能夠經過多種方式,包括使用 target Fragment APIs (Fragment.setTargetFragment()Fragment.getTargetFragment(),ViewModel 或者 使用 Fragments父容器 Activity,target Fragment APIs 已通過時了,如今鼓勵使用新的 Fragment result APIs 完成 Frrgament 之間傳遞數據,其中傳遞數據由 FragmentManager 處理,而且在 Fragments 設置發送數據和接受數據web

在 Frrgament 之間傳遞數據

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

接受數據

若是想在 Fragment 中接受數據,能夠在 FragmentManager 中註冊一個 FragmentResultListener,參數 requestKey 能夠過濾掉 FragmentManager 發送的數據:算法

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

參數 lifecycleOwner 能夠觀察生命週期,當 Fragment 的生命週期處於 STARTED 時接受數據。若是監聽 Fragment 的生命週期,您能夠在接收到新數據時安全地更新 UI,由於 view 的建立(onViewCreated() 方法在 onStart() 以前被調用)。安全

當生命週期處於 LifecycleOwner STARTED 的狀態以前,若是有多個數據傳遞,只會接收到最新的值微信

當生命週期處於 DESTROYED 時,它將自動移除 listener,若是想手動移除 listener,須要調用 FragmentManager.setFragmentResultListener() 方法,傳遞空的 FragmentResultListener:app

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 相同的 FragmentManageride

發送數據

若是 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 是如何作的。

源碼分析

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

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 之間通訊最好的方式是什麼?

參考文獻

  • Now in Android #17: https://medium.com/androiddeve......
  • Pass data between fragments: https://developer.android.com/training/basi......
  • ViewModel#sharing: https://developer.android.com/topic/librari......
  • 與其餘 Fragment 通訊: https://developer.android.com/training/basic......

結語

致力於分享一系列 Android 系統源碼、逆向分析、算法、翻譯相關的文章,目前正在翻譯一系列歐美精選文章,不只僅是翻譯,還有翻譯背後對每篇文章思考,若是你喜歡這片文章,請幫我點個贊,感謝,期待與你一塊兒成長!

---END---


推薦閱讀:
熱門Android Studio 插件,這裏是Top 20!
ViewHolder的MVVM實現
面試官問:說說消息隊列經常使用的幾種場景?
Java 編譯期與運行期,別傻傻分不清楚!
B站可能真的變了。
谷歌將遏制OEM廠商殺後臺問題 安卓11不會加入長截圖功能
使用 ConcatAdapter 順序鏈接其餘 Adapter
大量網友QQ號「被凍結」,蘋果安卓都中招!騰訊官方緊急迴應……
Kotlin擴展函數 + DSL 封裝Adapter,是時候提升你擼RecycleView的效率了!


更文不易,點個「在看」支持一下👇

本文分享自微信公衆號 - 技術最TOP(Tech-Android)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索