activity 点击后传递数据给fragment_Fragment 新特性 : Fragment Result API 使用以及源码分析
- 原標題: Android Fragments: Fragment Result
- 原文地址: https://proandroiddev.com/android-fragments-fragment-result......
- 原文作者: Husayn Hakeem
今年 Google 推出了 Fragment Result API 和 Activity Results API,用來取代之前的 Activity 和 Fragment 之間通信方式的不足。
這篇文章大概是我在 5 月份的寫的,主要介紹 Fragment Result API,分為 譯文 和 譯者的思考 兩個部分。
Fragment Result API 主要介紹 Fragment 間通信的新方式,是在 Fragment 1.3.0-alpha04 新增加的 API ,而現在最新版本已經到 fragment-1.3.0-beta01 應該很快就能應用在項目里面了。
接下來分析一下 Fragment Result API 主要為我們解決了什么問題,它都有那些更新。
通過這篇文章你將學習到以下內容,將在譯者思考部分會給出相應的答案
- 新 Fragment 間通信的方式的使用?
- 新 Fragment 間通信的源碼分析?
- 匯總 Fragment 之間的通信的方式?
譯文
Frrgament 間傳遞數據可以通過多種方式,包括使用 target Fragment APIs (Fragment.setTargetFragment() 和 Fragment.getTargetFragment()),ViewModel 或者 使用 Fragments’ 父容器 Activity,target Fragment APIs 已經過時了,現在鼓勵使用新的 Fragment result APIs 完成 Frrgament 之間傳遞數據,其中傳遞數據由 FragmentManager 處理,并且在 Fragments 設置發送數據和接受數據
在 Frrgament 之間傳遞數據
使用新的 Fragment APIs 在 兩個 Frrgament 之間的傳遞,沒有任何引用,可以使用它們公共的 FragmentManager,它充當 Frrgament 之間傳遞數據的中心存儲。
接受數據
如果想在 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
- 如果在 FragmentA 中接受 FragmentB 發送的數據,FragmentA 是 FragmentB 的父容器, 他們通過 child FragmentManager 進行通信
listener 必須設置的Fragment 相同的 FragmentManager
發送數據
如果 FragmentB 發送數據給 FragmentA,需要在 FragmentA 中注冊 listener,通過 parent FragmentManager 發送數據
parentFragmentManager.setFragmentResult(requestKey, // Same request key FragmentA used to register its listenerbundleOf(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 managerscenario.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 UIonView(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 listenervar receivedData = ""scenario.onFragment { fragment ->fragment.parentFragmentManager.setFragmentResultListener(KEY,fragment,FragmentResultListener { key, result ->receivedData = result.getString(KEY_DATA)})}// Send dataonView(withId(R.id.send_data)).perform(click())// Verify data was successfully sentassertThat(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 listenerbundleOf(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 對應的 listenerif (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() {@Overridepublic 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 對應的 BundlemResults.remove(requestKey);return;}// mResultListeners 是 ConcurrentHashMap 的實例,用來儲存注冊的 listener// 獲取 requestKey 對應的 listenerLifecycleAwareResultListener 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 系統源碼、逆向分析、算法、翻譯、Jetpack 源碼相關的文章,在技術的道路上一起前進
最后推薦我一直在更新維護的項目和網站:
- 計劃建立一個最全、最新的 AndroidX Jetpack 相關組件的實戰項目 以及 相關組件原理分析文章,正在逐漸增加 Jetpack 新成員,倉庫持續更新,歡迎前去查看:
- LeetCode / 劍指 offer / 國內外大廠面試題 / 多線程 題解,語言 Java 和 kotlin,包含多種解法、解題思路、時間復雜度、空間復雜度分析
- 劍指 offer 及國內外大廠面試題解:
- LeetCode 系列題解:
- 最新 Android 10 源碼分析系列文章,了解系統源碼,不僅有助于分析問題,在面試過程中,對我們也是非常有幫助的,倉庫持續更新,歡迎前去查看
- 整理和翻譯一系列精選國外的技術文章,每篇文章都會有譯者思考部分,對原文的更加深入的解讀,倉庫持續更新,歡迎前去查看
- 「為互聯網人而設計,國內國外名站導航」涵括新聞、體育、生活、娛樂、設計、產品、運營、前端開發、Android 開發等等網址,歡迎前去查看
總結
以上是生活随笔為你收集整理的activity 点击后传递数据给fragment_Fragment 新特性 : Fragment Result API 使用以及源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 北京发现一起涉酒吧聚集性疫情:依然存在隐
- 下一篇: 奶茶没“奶”成业内常态:喜茶呼吁行业使用