作者: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 之間的通信的方式:綜合以上通信方式,那麼你認為 Fragment 之間通信最好的方式是什麼?
BATcoder是Android進階三部曲番外篇,會以視頻形式分享給大家。Android開發可掃碼添加皇叔微信!記得備註:BATcoder,可更快被通過且邀請進群