activity 点击后传递数据给fragment_Fragment 新特性 : Fragment Result API 使用以及源码分析
- 原標(biāo)題: Android Fragments: Fragment Result
- 原文地址: https://proandroiddev.com/android-fragments-fragment-result......
- 原文作者: Husayn Hakeem
今年 Google 推出了 Fragment Result API 和 Activity Results API,用來(lái)取代之前的 Activity 和 Fragment 之間通信方式的不足。
這篇文章大概是我在 5 月份的寫的,主要介紹 Fragment Result API,分為 譯文 和 譯者的思考 兩個(gè)部分。
Fragment Result API 主要介紹 Fragment 間通信的新方式,是在 Fragment 1.3.0-alpha04 新增加的 API ,而現(xiàn)在最新版本已經(jīng)到 fragment-1.3.0-beta01 應(yīng)該很快就能應(yīng)用在項(xiàng)目里面了。
接下來(lái)分析一下 Fragment Result API 主要為我們解決了什么問題,它都有那些更新。
通過(guò)這篇文章你將學(xué)習(xí)到以下內(nèi)容,將在譯者思考部分會(huì)給出相應(yīng)的答案
- 新 Fragment 間通信的方式的使用?
- 新 Fragment 間通信的源碼分析?
- 匯總 Fragment 之間的通信的方式?
譯文
Frrgament 間傳遞數(shù)據(jù)可以通過(guò)多種方式,包括使用 target Fragment APIs (Fragment.setTargetFragment() 和 Fragment.getTargetFragment()),ViewModel 或者 使用 Fragments’ 父容器 Activity,target Fragment APIs 已經(jīng)過(guò)時(shí)了,現(xiàn)在鼓勵(lì)使用新的 Fragment result APIs 完成 Frrgament 之間傳遞數(shù)據(jù),其中傳遞數(shù)據(jù)由 FragmentManager 處理,并且在 Fragments 設(shè)置發(fā)送數(shù)據(jù)和接受數(shù)據(jù)
在 Frrgament 之間傳遞數(shù)據(jù)
使用新的 Fragment APIs 在 兩個(gè) Frrgament 之間的傳遞,沒有任何引用,可以使用它們公共的 FragmentManager,它充當(dāng) Frrgament 之間傳遞數(shù)據(jù)的中心存儲(chǔ)。
接受數(shù)據(jù)
如果想在 Fragment 中接受數(shù)據(jù),可以在 FragmentManager 中注冊(cè)一個(gè) FragmentResultListener,參數(shù) requestKey 可以過(guò)濾掉 FragmentManager 發(fā)送的數(shù)據(jù)
FragmentManager.setFragmentResultListener(requestKey,lifecycleOwner,FragmentResultListener { requestKey: String, result: Bundle ->// Handle result})參數(shù) lifecycleOwner 可以觀察生命周期,當(dāng) Fragment 的生命周期處于 STARTED 時(shí)接受數(shù)據(jù)。如果監(jiān)聽 Fragment 的生命周期,您可以在接收到新數(shù)據(jù)時(shí)安全地更新 UI,因?yàn)?view 的創(chuàng)建(onViewCreated() 方法在 onStart() 之前被調(diào)用)。
當(dāng)生命周期處于 LifecycleOwner STARTED 的狀態(tài)之前,如果有多個(gè)數(shù)據(jù)傳遞,只會(huì)接收到最新的值
當(dāng)生命周期處于 LifecycleOwner DESTROYED 時(shí),它將自動(dòng)移除 listener,如果想手動(dòng)移除 listener,需要調(diào)用 FragmentManager.setFragmentResultListener() 方法,傳遞空的 FragmentResultListener
在 FragmentManager 中注冊(cè) listener,依賴于 Fragment 發(fā)送返回的數(shù)據(jù)
- 如果在 FragmentA 中接受 FragmentB 發(fā)送的數(shù)據(jù),FragmentA 和 FragmentB 處于相同的層級(jí),通過(guò) parent FragmentManager 進(jìn)行通信,FragmentA 必須使用 parent FragmentManager 注冊(cè) listener
- 如果在 FragmentA 中接受 FragmentB 發(fā)送的數(shù)據(jù),FragmentA 是 FragmentB 的父容器, 他們通過(guò) child FragmentManager 進(jìn)行通信
listener 必須設(shè)置的Fragment 相同的 FragmentManager
發(fā)送數(shù)據(jù)
如果 FragmentB 發(fā)送數(shù)據(jù)給 FragmentA,需要在 FragmentA 中注冊(cè) listener,通過(guò) parent FragmentManager 發(fā)送數(shù)據(jù)
parentFragmentManager.setFragmentResult(requestKey, // Same request key FragmentA used to register its listenerbundleOf(key to value) // The data to be passed to FragmentA )測(cè)試 Fragment Results
測(cè)試 Fragment 是否成功接收或發(fā)送數(shù)據(jù),可以使用 FragmentScenario API
接受數(shù)據(jù)
如果在 FragmentA 中注冊(cè) FragmentResultListener 接受數(shù)據(jù),你可以模擬 parent FragmentManager 發(fā)送數(shù)據(jù),如果在 FragmentA 中正確注冊(cè)了 listener,可以用來(lái)驗(yàn)證 FragmentA 是否能收到數(shù)據(jù),例如,如果在 FragmentA 中接受數(shù)據(jù)并更新 UI, 可以使用 Espresso APIs 來(lái)驗(yàn)證是否期望的數(shù)據(jù)
@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"))) }發(fā)送數(shù)據(jù)
可以在 FragmentB 的 parent FragmentManager 上注冊(cè)一個(gè) FragmentResultListener 來(lái)測(cè)試 FragmentB 是否成功發(fā)送數(shù)據(jù),當(dāng)發(fā)送數(shù)據(jù)結(jié)束時(shí),可以來(lái)驗(yàn)證這個(gè) listener 是否能收到數(shù)據(jù)
@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") }總結(jié)
雖然使用了 Fragment result APIs,替換了過(guò)時(shí)的 Fragment target APIs,但是新的 APIs 在Bundle 作為數(shù)據(jù)傳傳遞方面有一些限制,只能傳遞簡(jiǎn)單數(shù)據(jù)類型、Serializable 和 Parcelable 數(shù)據(jù),Fragment result APIs 允許程序從崩潰中恢復(fù)數(shù)據(jù),而且不會(huì)持有對(duì)方的引用,避免當(dāng) Fragment 處于不可預(yù)知狀態(tài)的時(shí),可能發(fā)生未知的問題
譯者的思考
這是譯者的一些思考,總結(jié)一下 Fragment 1.3.0-alpha04 新增加的 Fragment 間通信的 API
數(shù)據(jù)接受
FragmentManager.setFragmentResultListener(requestKey,lifecycleOwner,FragmentResultListener { requestKey: String, result: Bundle ->// Handle result})數(shù)據(jù)發(fā)送
parentFragmentManager.setFragmentResult(requestKey, // Same request key FragmentA used to register its listenerbundleOf(key to value) // The data to be passed to FragmentA )那么 Fragment 間通信的新 API 給我們帶來(lái)哪些好處呢:
- 在 Fragment 之間傳遞數(shù)據(jù),不會(huì)持有對(duì)方的引用
- 當(dāng)生命周期處于 ON_START 時(shí)開始處理數(shù)據(jù),避免當(dāng) Fragment 處于不可預(yù)知狀態(tài)的時(shí),可能發(fā)生未知的問題
- 當(dāng)生命周期處于 ON_DESTROY 時(shí),移除監(jiān)聽
我們一起來(lái)從源碼的角度分析一下 Google 是如何做的
源碼分析
按照慣例從調(diào)用的方法來(lái)分析,數(shù)據(jù)接受時(shí),調(diào)用了 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 的實(shí)例,用來(lái)儲(chǔ)存注冊(cè)的 listener// 如果傳遞的參數(shù) listener 為空時(shí),移除 requestKey 對(duì)應(yīng)的 listenerif (listener == null) {mResultListeners.remove(requestKey);return;}// Lifecycle是一個(gè)生命周期感知組件,一般用來(lái)響應(yīng)Activity、Fragment等組件的生命周期變化final Lifecycle lifecycle = lifecycleOwner.getLifecycle();// 當(dāng)生命周期處于 DESTROYED 時(shí),直接返回// 避免當(dāng) Fragment 處于不可預(yù)知狀態(tài)的時(shí),可能發(fā)生未知的問題if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {return;}// 開始監(jiān)聽生命周期LifecycleEventObserver observer = new LifecycleEventObserver() {@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {// 當(dāng)生命周期處于 ON_START 時(shí)開始處理數(shù)據(jù)if (event == Lifecycle.Event.ON_START) {// 開始檢查受到的數(shù)據(jù)Bundle storedResult = mResults.get(requestKey);if (storedResult != null) {// 如果結(jié)果不為空,調(diào)用回調(diào)方法listener.onFragmentResult(requestKey, storedResult);// 清除數(shù)據(jù)setFragmentResult(requestKey, null);}}// 當(dāng)生命周期處于 ON_DESTROY 時(shí),移除監(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是一個(gè)生命周期感知組件,一般用來(lái)響應(yīng)Activity、Fragment等組件的生命周期變化
- 獲取 Lifecycle 去監(jiān)聽 Fragment 的生命周期的變化
- 當(dāng)生命周期處于 ON_START 時(shí)開始處理數(shù)據(jù),避免當(dāng) Fragment 處于不可預(yù)知狀態(tài)的時(shí),可能發(fā)生未知的問題
- 當(dāng)生命周期處于 ON_DESTROY 時(shí),移除監(jiān)聽
接下來(lái)一起來(lái)看一下數(shù)據(jù)發(fā)送的方法,調(diào)用了 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 的實(shí)例,用來(lái)存儲(chǔ)數(shù)據(jù)傳輸?shù)?Bundle// 如果傳遞的參數(shù) result 為空,移除 requestKey 對(duì)應(yīng)的 BundlemResults.remove(requestKey);return;}// mResultListeners 是 ConcurrentHashMap 的實(shí)例,用來(lái)儲(chǔ)存注冊(cè)的 listener// 獲取 requestKey 對(duì)應(yīng)的 listenerLifecycleAwareResultListener resultListener = mResultListeners.get(requestKey);if (resultListener != null && resultListener.isAtLeast(Lifecycle.State.STARTED)) {// 如果 resultListener 不為空,并且生命周期處于 STARTED 狀態(tài)時(shí),調(diào)用回調(diào)resultListener.onFragmentResult(requestKey, result);} else {// 否則保存當(dāng)前傳輸?shù)臄?shù)據(jù)mResults.put(requestKey, result);} }- 獲取 requestKey 注冊(cè)的 listener
- 當(dāng)生命周期處于 STARTED 狀態(tài)時(shí),開始發(fā)送數(shù)據(jù)
- 否則保存當(dāng)前傳輸?shù)臄?shù)據(jù)
源碼分析到這里結(jié)束了,我們一起來(lái)思考一下,在之前我們的都有那些數(shù)據(jù)傳方式
匯總 Fragment 之間的通信的方式
- 通過(guò)共享 ViewModel 或者關(guān)聯(lián) Activity來(lái)完成,Fragment 之間不應(yīng)該直接通信 參考 Google: ViewModel#sharing
- 通過(guò)接口,可以在 Fragment 定義接口,并在 Activity 實(shí)現(xiàn)它 參考 Google: 與其他 Fragment 通信
- 通過(guò)使用 findFragmentById 方法,獲取 Fragment 的實(shí)例,然后調(diào)用 Fragment 的公共方法 參考 Google: 與其他 Fragment 通信
- 調(diào)用 Fragment.setTargetFragment() 和 Fragment.getTargetFragment() 方法,但是注意 target fragment 需要直接訪問另一個(gè) fragment 的實(shí)例,這是十分危險(xiǎn)的,因?yàn)槟悴恢滥繕?biāo) fragment 處于什么狀態(tài)
- Fragment 新的 API, setFragmentResult() 和 setFragmentResultListener()
綜合以上通信方式,那么你認(rèn)為 Fragment 之間通信最好的方式是什么?
參考文獻(xiàn)
- 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......
結(jié)語(yǔ)
全文到這里就結(jié)束了,如果有幫助 點(diǎn)個(gè)贊 就是對(duì)我最大的鼓勵(lì)!
致力于分享一系列 Android 系統(tǒng)源碼、逆向分析、算法、翻譯、Jetpack 源碼相關(guān)的文章,在技術(shù)的道路上一起前進(jìn)
最后推薦我一直在更新維護(hù)的項(xiàng)目和網(wǎng)站:
- 計(jì)劃建立一個(gè)最全、最新的 AndroidX Jetpack 相關(guān)組件的實(shí)戰(zhàn)項(xiàng)目 以及 相關(guān)組件原理分析文章,正在逐漸增加 Jetpack 新成員,倉(cāng)庫(kù)持續(xù)更新,歡迎前去查看:
- LeetCode / 劍指 offer / 國(guó)內(nèi)外大廠面試題 / 多線程 題解,語(yǔ)言 Java 和 kotlin,包含多種解法、解題思路、時(shí)間復(fù)雜度、空間復(fù)雜度分析
- 劍指 offer 及國(guó)內(nèi)外大廠面試題解:
- LeetCode 系列題解:
- 最新 Android 10 源碼分析系列文章,了解系統(tǒng)源碼,不僅有助于分析問題,在面試過(guò)程中,對(duì)我們也是非常有幫助的,倉(cāng)庫(kù)持續(xù)更新,歡迎前去查看
- 整理和翻譯一系列精選國(guó)外的技術(shù)文章,每篇文章都會(huì)有譯者思考部分,對(duì)原文的更加深入的解讀,倉(cāng)庫(kù)持續(xù)更新,歡迎前去查看
- 「為互聯(lián)網(wǎng)人而設(shè)計(jì),國(guó)內(nèi)國(guó)外名站導(dǎo)航」涵括新聞、體育、生活、娛樂、設(shè)計(jì)、產(chǎn)品、運(yùn)營(yíng)、前端開發(fā)、Android 開發(fā)等等網(wǎng)址,歡迎前去查看
總結(jié)
以上是生活随笔為你收集整理的activity 点击后传递数据给fragment_Fragment 新特性 : Fragment Result API 使用以及源码分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 北京发现一起涉酒吧聚集性疫情:依然存在隐
- 下一篇: 奶茶没“奶”成业内常态:喜茶呼吁行业使用