新特性 !帶你解鎖Fragment 間通信的新方式

2021-02-16 劉望舒

作者:HiDhl
https://juejin.im/post/5eb58da05188256d6d6bb248
本文由作者授權發布。

就在 2020/05/07 號 Now in Android #17 更新了,發布 Android 的新特性,其中就包括 Fragment 間通信的新方式,大家可以前往,看看都有那些更新。

https://medium.com/androiddevelopers/now-in-android-17-9d73f7bed7f

通過這篇文章你將學習到以下內容,將在譯者思考部分會給出相應的答案

新 Fragment 間通信的方式的使用?

新 Fragment 間通信的源碼分析?

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

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

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

使用新的 Fragment APIs 在 兩個 Fragment 之間的傳遞,沒有任何引用,可以使用它們公共的  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 的狀態之前,如果有多個數據傳遞,只會接收到最新的值:

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

在 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 相同的 FragmentManager。

發送數據

如果 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 是否成功接收或發送數據,可以使用 FragmentScenario API

https://developer.android.com/reference/androidx/fragment/app/testing/FragmentScenario接受數據

如果在 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 是如何做的。

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

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);
    }
}


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

匯總 Fragment 之間的通信的方式:
1. 通過共享 ViewModel 或者關聯 Activity來完成,Fragment 之間不應該直接通信 參考 https://developer.android.com/topic/libraries/architecture/viewmodel#sharing
2. 通過接口,可以在 Fragment 定義接口,並在 Activity 實現它https://developer.android.com/training/basics/fragments/communicating
3. 通過使用 findFragmentById 方法,獲取 Fragment 的實例,然後調用 Fragment 的公共方法 https://developer.android.com/training/basics/fragments/communicating4. 調用 Fragment.setTargetFragment() 和 Fragment.getTargetFragment() 方法,但是注意 target fragment 需要直接訪問另一個 fragment 的實例,這是十分危險的,因為你不知道目標 fragment 處於什麼狀態
5. Fragment 新的 API, setFragmentResult() 和 setFragmentResultListener()

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


  END 

BATcoder是Android進階三部曲番外篇,會以視頻形式分享給大家。Android開發可掃碼添加皇叔微信!記得備註:BATcoder,可更快被通過且邀請進群

相關焦點

  • 2020 年 Fragment 最新文檔(下),該更新知識庫啦
    在系統資源回收(進程死亡並重新創建)之後,將重新創建  ViewModel,並生成一個新種子。在 ViewModel 中添加 SavedState 模塊可以使 ViewModel 在系統資源回收的場景下保留其內部數據。通信為了復用 fragment,需要將每個 fragment 構建為完全獨立的組件並定義自己的布局和行為。
  • 學習安卓開發[2]-在Activity中託管Fragment
    在上一篇學習安卓開發[1]-程序結構、Activity生命周期及頁面通信中,學習了Activity的一些基礎應用,基於這些知識,可以構建一些簡單的APP了,但這還遠遠不夠,本節會學習如何使用Activity託管Fragment的方式來進行開發
  • 德州儀器TI:5G無線應用以三種方式解鎖2019新技術時代
    5G無線應用以三種方式解鎖2019新技術時代 試想下這樣的場景:當災難發生後,官方發射小型但功能強大的蜂窩行動裝置無人機,從而使網絡再次運行。1「在即將到來的數字世界人類仍將佔據主導,5G的到來意味著我們和機器之間可以實現更好、更快的通信。它將改變我們與物體和彼此間的互動方式。」 閱讀TI白皮書「為5G世界做準備:對支持技術和硬體要求的概述」。 5G為人類和機器提供更好的通信這種新型蜂窩技術標準在目前4G的功能基礎上實現了數量級的提升。
  • 小米平板4新特性曝光:支持AI人臉識別,看一眼疾速解鎖!
    最近,小米宣布平板4回歸以後,幾乎是每一天都要預告一點它的新特性。今天上午,小米官方又公布了一個小米平板4的新特性—支持AI人臉識別,看一眼疾速解鎖。從官方發布的海報中,我們可以看出,小米巧妙的運用了足球中換人的方式,即指紋識別下場,人臉識別上場。所以此次小米平板4有了人臉識別解鎖的加持,相信在用戶體驗上絕對會有很大的提高。
  • 關於Fragment你所需知道的一切
    來自官方的也是最全面的譯文資料,這裡有你想要了解的關於Fragment的一切。Fragment 是 Android 3.0 (API level 11) 新加入的API,主要的設置目的是為了使UI在不同的屏幕上表現得更加靈活。
  • 「對話AI,認知AI,情感AI」竹間智能產品發布會帶你解鎖新技能
    2020開年的一場疫情,使得人們的溝通不得不從線下轉為線上,人機互動逐漸成為主流;新基建被重新定義後,人工智慧進入應用落地爆發期,NLP作為人工智慧領域的明珠,必將引領新變革;對話AI,認知AI,情感AI,讓人工智慧賦能萬物;竹間智能Bot Factory2020發布會即將開啟;多項技術場景升級 開放平臺重磅發布發布會看點揭秘作為一站式企業級的情感人工智慧平臺,Bot Factory2020提供快速搭建智能對話機器人的能力。
  • 小米平板4重磅新特性:支持AI人臉識別 看一眼疾速解鎖
    今天,小米官方再次公布了小米平板4的新特性:將支持AI人臉識別,看一眼疾速解鎖。據小米公司官方微博發布的內容,小米平板4將支持AI人臉識別,更快捷的解鎖方式,看一眼就解鎖。配圖同樣結合了世界盃元素,以人臉識別技術換下指紋識別技術。而隨著人臉識別技術的日漸成熟,越來越多的新品手機開始採用,而加入到平板電腦中,目前並不多。
  • 奶爸帶娃又解鎖新方式,化身「移動籃球架」,萌娃邊走路邊投球
    奶爸帶娃又解鎖新方式,化身「移動籃球架」,萌娃邊走路邊投球寶爸們帶娃總能不斷出現極佳的創意新方式,一位網友就將丈夫陪兒子玩籃球的視頻拍攝下來上傳到了網絡上,立刻引起了眾多網友點讚和評論。看到這個視頻的網友都笑稱,「機智奶爸」解鎖帶娃新方式,化身「移動籃球架」,看來回家要給自家孩子也整一個。還有的網友表示,爸爸積極參與帶孩子的家庭,孩子的童年才能更開心。
  • 這個假期,你解鎖了哪些新技能?
    正所謂「一時運動一時爽,一直運動一直爽」,如果你也正在解鎖「健身達人」的技能,可不要輕言放棄。03「軟體收割機」待在家的日子裡,學員們解鎖了使用PR、PS、AE等軟體的技能。他們親手製作了為武漢加油的海報和視頻,用自己的方式,為「第二故鄉」武漢加油……
  • 水聲通信技術的發展及特性分析
    原始的海上通信方式包括烽火、信號彈、旗語等,到了電氣資訊時代,產生了現代化的通信手段。現在的海上通信包括水上通信和水下通信兩種形式。由於海上通信主要是船艦、潛艇等移動物體之間的通信,因此主要是無線通信,不考慮有線通信。而水上無線通信環境完全相似與陸地的無線通信環境,因此完全可以使用無線電通信系統。
  • 新方法打破互易定理為新一代通信系統鋪路
    無論是光波還是聲波或者其他類型的波,都是以相同的方式向前和向後傳播。如果我們可以讓波僅沿著一個方向傳播,打破互易性,那麼就可以改變我們日常生活中許多重要的應用。例如,我們可以構造出新型「單向」元件,比如環行器和隔離器,從而實現雙向通信,使無線通信系統的數據容量翻倍。
  • 提前預約你的通信展時間,一起「雲上」觀展吧
    絕美的5G珠峰慢直播5G加持下,閱兵更振奮人心緊張刺激的5G賽事直播2020年是直播爆發元年,從年初的全民圍觀「火神山」「雷神山」建設,到雲直播看遍祖國山川美景,再到直播帶貨引發消費者購物狂潮從專業媒體的專業視角出發,以記者逛展的形式巧妙切入本次通信展,藉由通信世界成熟的直播流程及平臺支撐,將廠商參與本次展會的亮點通過線上的方式向更多觀眾介紹展示。屆時,足不出戶的你也能將展會逛透透~運營商過去一年時間5G建設取得了哪些突破?產業鏈上下遊廠商又將拿出怎樣的成果展示?
  • 進程間通信方式有哪些?
    前言進程能夠單獨運行並且完成一些任務,但是也經常免不了和其他進程傳輸數據或互相通知消息,即需要進行通信,本文將簡單介紹一些進程之間相互通信的技術--進程間通信(InterProcess Communication,IPC)。由於篇幅有限,本文不會對每一種進行詳細介紹。
  • 初春來襲,歐舒丹帶你解鎖SPA級沐浴新搭配
    來源標題:初春來襲,歐舒丹帶你解鎖SPA級沐浴新搭配最近天氣漸漸回暖,小仙女們都褪去厚重的大衣,換上清爽的碎花裙。雖然春天空氣溼潤很多,但我們的肌膚經過一整個冬天的「摧殘」,仍會拔幹起皮。如何快速地讓肌膚在這充滿活力的季節趕緊復甦過來呢?
  • Fragment 番外篇——TabLayout+ViewPager+Fragment
    前言 上一篇文章中我們使用底部導航+Fragment的方式實現了Android主流App中大都存在的設計。並命名其為「Fragment最佳實踐」,作為想到單獨使用Fragment的用戶來說,這個說法並不誇大,它解決了許多用戶在使用Fragment時產生的這樣那樣可見或不可見的問題。不過Fragment還有其他的使用方式,就是我們本章要介紹的。
  • 妙可藍多新吃法,解鎖美味的旅程
    上一次和大家分享了妙可藍多奶酪棒正確的打開方式是不是讓很多寶媽寶爸都腦洞大開為孩子嘗試了不少新吃法呢?今天機智的小編又深扒出一波高能妙可藍多奶酪棒打開方式帶大家發現更多美好場景創造無限快樂~準備好了嗎?讓我們開始吧!
  • 總裁直播新勢力!帶你解鎖小狗京東直播間新玩法!
    如今,直播行業的帶貨熱潮仍在不斷升溫,眾多高新科技企業的總裁紛紛親自下場自家直播間,在傳播企業文化的同時也為品牌賦能。小狗吸塵器作為專注吸塵領域21年的專業品牌,又如何能缺席這樣的盛宴?  據悉,小狗吸塵器將於8月13日晚22:00,在京東小狗電器官方旗艦店直播間準時開啟直播。屆時,小狗吸塵器的總裁檀衝也將蒞臨直播間,為廣大用戶帶來小狗最強寵粉福利。
  • 《緋雨騎士團2》新職業怎麼解鎖 新職業解鎖條件一覽
    導 讀 緋雨騎士團2中玩家是可以在遊戲中解鎖新職業的,不過需要滿足一些條件,可能有些萌新玩家還不知道在哪裡解鎖新職業
  • 《最強蝸牛》米國特性解鎖方法
    《最強蝸牛》米國特性解鎖方法 最強蝸牛中國家特性是非常重要的一類BUFF,那麼最強蝸牛米國特性怎麼開呢
  • 現代通信理論與新技術 PPT整理
    現代通信理論與新技術緒論概述衛星通信簡介光纖通信簡介移動通信簡介光纖傳輸網技術基本概念基本原理(全反射)SDH光傳輸網大容量光傳輸技術波分復用WDM光時分復用OTDM光碼分復用OCDM光交換技術數字微波通信技術移動通信網絡基本概念多址接入技術OFDM技術的基本原理多輸入多輸出MIMO技術的基本原理無線網絡的媒體接入控制層MAC協議ALOHA協議CSMA協議寬帶無線行動網路中的自組織網絡無線傳感網絡綜述