這是我參與更文挑戰的第8天,活動詳情查看: 更文挑戰java
Fragment 間的通訊能夠藉助如下幾種方式實現:android
EventBus 的優缺點都很突出。 優勢是限制少可隨意使用,缺點是限制太少使用太隨意。markdown
由於 EventBus 會致使開發者在架構設計上「不思進取」,隨着項目變複雜,結構愈來愈混亂,代碼可讀性變差,數據流的變化難以追蹤。架構
因此,規模越大的項目 EvenBus 的負面效果越明顯,所以不少大廠都禁止 EventBus 的使用。因此這道題千萬不要把 EventBus 做爲首選答案,比較得體的回答是:app
「 EventBus 具有通訊能力,可是缺點很突出,大量使用 EventBus 會形成項目難以維護、問題難以定位,因此我不建議在項目中使用 EventBus 進行通訊。 」ide
爲了迭代更加敏捷,Fragment 從 AOSP 遷移到了 AndroidX ,這致使同時存在着兩種包名的 Fragment:android.app.Fragment
和 andoridx.fragment.app.Fragment
。函數
雖然前者已經被廢棄,但不少歷史代碼中尚存, 對於老的Fragment,常常依賴基於 Activity 的通訊方式,由於其餘通訊方式大都依賴 AndroidX 。工具
class MainActivity : AppCompatActivity() {
val listFragment: ListFragment by lazy {
ListFragment()
}
val CreatorFragment: CreatorFragment by lazy {
// 構建Fragment的時候設置 Callback,創建通訊
CreatorFragment().apply {
setOnItemCreated {
listFragment.addItem(it)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.beginTransaction().apply {
add(R.id.fragmentContainer, listFragment)
commit()
}
}
}
複製代碼
如上,在 Activity 或父 Fragment 中建立子Fragment,同時爲其設置 Callbackpost
此時,Fragment 的建立依賴手動配置,沒法在 ConfigurationChangeed 的時候自動恢復重建,因此除了用來處理 android.app.Fragment
的歷史遺留代碼以外,不推薦使用。ui
ViewModel 是目前使用最普遍的通訊方式之一,在 Kotlin 中使用時,須要引入fragment-ktx
class ListViewModel : ViewModel() {
private val originalList: LiveData<List<Item>>() = ...
val itemList: LiveData<List<Item>> = ...
fun addItem(item: Item) {
//更新 LiveData
}
}
class ListFragment : Fragment() {
// 藉助ktx,使用activityViewModels()代理方式獲取ViewModel
private val viewModel: ListViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.itemList.observe(viewLifecycleOwner, Observer { list ->
// Update the list UI
}
}
}
class CreatorFragment : Fragment() {
private val viewModel: ListViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
button.setOnClickListener {
val item = ...
viewModel.addItem(item)
}
}
}
複製代碼
如上,經過訂閱 ViewModel 的 LiveData,接受數據變通的通知。由於兩個 Fragment 須要共享ViewModel,因此 ViewModel 必須在 Activity 的 Scope 中建立
關於 ViewModel 的實現原理,相關文章不少,本文不作贅述了。接下來重點看一下 Result API:
從Fragment 1.3.0-alpha04
起,FragmentManager 新增了 FragmentResultOwner
接口,顧名思義 FragmentManager 成爲了 FragmentResult 的持有者,能夠進行 Fragment 之間的通訊。
假設須要在 FragmentA 監聽 FragmentB 返回的數據,首先在 FragmentA 設置監聽
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setFragmentResultListener 是 fragment-ktx 提供的擴展函數
setFragmentResultListener("requestKey") { requestKey, bundle ->
// 監聽key爲「requestKey」的結果, 並經過bundle獲取
val result = bundle.getString("bundleKey")
// ...
}
}
// setFragmentResultListener 是Fragment的擴展函數,內部調用 FragmentManger 的同名方法
public fun Fragment.setFragmentResultListener(
requestKey: String,
listener: ((requestKey: String, bundle: Bundle) -> Unit)
) {
parentFragmentManager.setFragmentResultListener(requestKey, this, listener)
}
複製代碼
當從 FragmentB 返回結果時:
button.setOnClickListener {
val result = "result"
setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}
//setFragmentResult 也是 Fragment 的擴展函數,其內部調用 FragmentManger 的同名方法
public fun Fragment.setFragmentResult(requestKey: String, result: Bundle) {
parentFragmentManager.setFragmentResult(requestKey, result)
}
複製代碼
上面的代碼能夠用下圖表示:
Result API的原理很是簡單,FragmentA 經過 Key 向 FragmentManager 註冊 ResultListener
,FragmentB 返回 result 時, FM 經過 Key 將結果回調給FragmentA 。須要特別注意的是隻有當 FragmentB 返回時,result纔會被真正回傳,若是 setFragmentResult
屢次,則只會保留最後一次結果。
經過梳理源碼能夠知道Result API是LifecycleAware的
源碼基於 androidx.fragment:fragment:1.3.0
//FragmentManager.java
private final Map<String, LifecycleAwareResultListener> mResultListeners =
Collections.synchronizedMap(new HashMap<String, LifecycleAwareResultListener>());
public final void setFragmentResultListener(@NonNull final String requestKey, @NonNull final LifecycleOwner lifecycleOwner, @NonNull final FragmentResultListener listener) {
final Lifecycle lifecycle = lifecycleOwner.getLifecycle();
LifecycleEventObserver observer = new LifecycleEventObserver() {
if (event == Lifecycle.Event.ON_START) {
// once we are started, check for any stored results
Bundle storedResult = mResults.get(requestKey);
if (storedResult != null) {
// if there is a result, fire the callback
listener.onFragmentResult(requestKey, storedResult);
// and clear the result
clearFragmentResult(requestKey);
}
}
if (event == Lifecycle.Event.ON_DESTROY) {
lifecycle.removeObserver(this);
mResultListeners.remove(requestKey);
}
};
lifecycle.addObserver(observer);
LifecycleAwareResultListener storedListener = mResultListeners.put(requestKey,
new LifecycleAwareResultListener(lifecycle, listener, observer));
if (storedListener != null) {
storedListener.removeObserver();
}
}
複製代碼
listener.onFragmentResult
在 Lifecycle.Event.ON_START
的時候才調用,也就是說只有當 FragmentA 返回到前臺時,纔會收到結果,這與 LiveData 的邏輯的行爲一致,都是 LifecycleAware 的
當屢次調用 setFragmentResultListener
時, 會建立新的 LifecycleEventObserver
對象, 同時舊的 observer 會隨着 storedListener.removeObserver()
從 lifecycle 中移除,不能再被回調。
也就是說,對於同一個 requestKey 來講,只有最後一次設置的 listener 有效,這好像也是理所應當的,畢竟不叫 addFragmentResultListener
。
private final Map<String, Bundle> mResults =
Collections.synchronizedMap(new HashMap<String, Bundle>());
public final void setFragmentResult(@NonNull String requestKey, @NonNull Bundle result) {
// Check if there is a listener waiting for a result with this key
LifecycleAwareResultListener resultListener = mResultListeners.get(requestKey);
// if there is and it is started, fire the callback
if (resultListener != null && resultListener.isAtLeast(Lifecycle.State.STARTED)) {
resultListener.onFragmentResult(requestKey, result);
} else {
// else, save the result for later
mResults.put(requestKey, result);
}
}
複製代碼
setFragmentResult
很是簡單, 若是當前是 listener 處於前臺,則當即回調 setFragmentResult()
, 不然,存入 mResults
, 等待 listener 切換到前臺時再回調。
一個 listener 爲何有前臺/後臺的概念呢,這就是以前看到的 LifecycleAwareResultListener
了, 生命週期可感知是由於其內部持有一個 Lifecycle
, 而這個 Lifecycle 其實就是設置 listener 的那個 Fragment
private static class LifecycleAwareResultListener implements FragmentResultListener {
private final Lifecycle mLifecycle;
private final FragmentResultListener mListener;
private final LifecycleEventObserver mObserver;
LifecycleAwareResultListener(@NonNull Lifecycle lifecycle,
@NonNull FragmentResultListener listener,
@NonNull LifecycleEventObserver observer) {
mLifecycle = lifecycle;
mListener = listener;
mObserver = observer;
}
public boolean isAtLeast(Lifecycle.State state) {
return mLifecycle.getCurrentState().isAtLeast(state);
}
@Override
public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle result) {
mListener.onFragmentResult(requestKey, result);
}
public void removeObserver() {
mLifecycle.removeObserver(mObserver);
}
}
複製代碼
mResult
中的數據是會隨着 Fragment 的重建能夠恢復的,因此 FragmentA 永遠不會丟失 FragmentB 返回的結果。固然,一旦 Result 被消費,就會從 mResult
中清除
//FragmentManager.java
void restoreSaveState(@Nullable Parcelable state) {
//...
ArrayList<String> savedResultKeys = fms.mResultKeys;
if (savedResultKeys != null) {
for (int i = 0; i < savedResultKeys.size(); i++) {
mResults.put(savedResultKeys.get(i), fms.mResults.get(i));
}
}
}
複製代碼
Parcelable saveAllState() {
// FragmentManagerState implements Parcelable
FragmentManagerState fms = new FragmentManagerState();
//...
fms.mResultKeys.addAll(mResults.keySet());
fms.mResults.addAll(mResults.values());
//...
return fms;
}
複製代碼
ResultAPI 與 ViewModel + LiveData 有必定類似性,都是生命週期可感知的,均可以在恢復重建時保存數據,那這兩種通訊方式該如何選擇呢?
對此,官方給的建議以下:
The Fragment library provides two options for communication: a shared ViewModel and the Fragment Result API. The recommended option depends on the use case. To share persistent data with any custom APIs, you should use a ViewModel. For a one-time result with data that can be placed in a Bundle, you should use the Fragment Result API.
ResultAPI 主要適用於那些一次性的通訊場景(FragmentB返回結果後結束本身)。若是使用 ViewModel,須要上提到的 Fragment 共同的父級 Scope,而 Scope 的放大不利於數據的管理。
非一次性的通訊場景,因爲 FragmentA 和 FragmentB 在通訊過程當中共存,推薦經過共享 ViewModel 的方式,再借助 LiveData 等進行響應式通訊。
最後看一下,跨越不一樣 Activity 的 Fragmnet 間的通訊
跨 Activity 的通訊主要有兩種方式:
Result API出現以前,須要經過 startActivityResult
完成通訊,這也是 android.app.Fragment
惟一可選的方式。
通訊過程以下:
FragmentA 調用 startActivityForResult() 方法以後,跳轉到 ActivityB 中,ActivityB 把數據經過 setArguments()
設置給 FragmentB
FragmentB 調用 getActivity().setResult()
設置返回數據,FragmentA 在 onActivityResult()
中拿到數據
此時,有兩點須要特別注意:
不要使用 getActivity().startActivityForResult()
, 而是在Fragment中直接調用startActivityForResult()
activity 須要重寫 onActivityResult,其必須調用 super.onActivityResult(requestCode, resultCode, data)
以上兩點若是違反,則 onActivityResult 只可以傳遞到 activity 的,沒法傳遞到 Fragment
自1.3.0-alpha02
起,Fragment 支持 registerForActivityResult()
的使用,經過 Activity 的 ResultAPI 實現跨 Activity 通訊。
FragmentA 設置回調:
class FragmentA : Fragment() {
private val startActivityLauncher: ActivityResultLauncher<Intent> =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
//
} else if (it.resultCode == Activity.RESULT_CANCELED) {
//
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivityLauncher.launch(Intent(requireContext(), ActivityB::class.java))
}
}
複製代碼
FragmentB 返回結果
button.setOnClickListener {
val result = "result"
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}
複製代碼
瞭解 Activity Result API 的同窗對上述過程應該很熟悉。
簡單看一下源碼。
源碼基於 androidx.fragment:fragment:1.3.0
咱們在 FragmentA 中經過建立一個 ActivityResultLauncher
,而後調用 launch 啓動目標 ActivityB
//Fragment # prepareCallInternal
return new ActivityResultLauncher<I>() {
@Override
public void launch(I input, @Nullable ActivityOptionsCompat options) {
ActivityResultLauncher<I> delegate = ref.get();
if (delegate == null) {
throw new IllegalStateException("Operation cannot be started before fragment "
+ "is in created state");
}
delegate.launch(input, options);
}
//...
};
複製代碼
能夠看到,內部調用了delegate.launch
, 咱們追溯一下 delegate 的出處,即 ref
中設置的 value
//Fragment # prepareCallInternal
registerOnPreAttachListener(new OnPreAttachedListener() {
@Override
void onPreAttached() {
//ref中註冊了一個launcher,來自 registryProvider 提供的 ActivityResultRegistry
final String key = generateActivityResultKey();
ActivityResultRegistry registry = registryProvider.apply(null);
ref.set(registry.register(key, Fragment.this, contract, callback));
}
});
public final <I, O> ActivityResultLauncher<I> registerForActivityResult( @NonNull final ActivityResultContract<I, O> contract, @NonNull final ActivityResultCallback<O> callback) {
return prepareCallInternal(contract, new Function<Void, ActivityResultRegistry>() {
@Override
public ActivityResultRegistry apply(Void input) {
//registryProvider 提供的 ActivityResultRegistry 來自 Activity
if (mHost instanceof ActivityResultRegistryOwner) {
return ((ActivityResultRegistryOwner) mHost).getActivityResultRegistry();
}
return requireActivity().getActivityResultRegistry();
}
}, callback);
}
複製代碼
上面能夠看到 ref 中設置的 ActivityResultLauncher
來自 Activity 的 ActivityResultRegistry ,也就說 Fragment 的 launch,最終是由其 mHost 的 Activity 代理的。
後續也就是 Activity 的 Result API 的流程了,咱們知道 Activity Result API 本質上是基於 startActivityForResult 實現的,具體能夠參考這篇文章,本文再也不贅述了
本文總結了 Fragment 通訊的幾種常見方式,着重分析了 Result API
實現原理。 fragment-1.3.0
之後,對於一次性通訊推薦使用 Result API
替代舊有的 startActivityForResult
;響應式通訊場景則推薦使用 ViewModel + LiveData (or StateFlow)
, 儘可能避免使用 EventBus
這類工具進行通訊。