一道面試題:介紹一下 Fragment 間的通訊方式?

一道面試題:介紹一下 Fragment 間的通訊方式?

Fragment 間的通訊可以藉助以下幾種方式實現:

EventBus

Activity(or Parent Fragment)

ViewModel

Result API

1。 基於 EventBus 通訊

EventBus 的優缺點都很突出。 優點是限制少可隨意使用,缺點是限制太少使用太隨意。

因為 EventBus 會導致開發者在架構設計上“不思進取”,隨著專案變複雜,結構越來越混亂,程式碼可讀性變差,資料流的變化難以追蹤。

所以,規模越大的專案 EvenBus 的負面效果越明顯,因此很多大廠都禁止 EventBus 的使用。所以這道題千萬不要把 EventBus 作為首選答案,比較得體的回答是:

“ EventBus 具備通訊能力,但是缺點很突出,大量使用 EventBus 會造成專案難以維護、問題難以定位,所以我不建議在專案中使用 EventBus 進行通訊。 ”

2。 基於 Activity 或父 Fragment 通訊

為了迭代更加敏捷,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,同時為其設定 Callback

此時,Fragment 的建立依賴手動配置,無法在 ConfigurationChangeed 的時候自動恢復重建,所以除了用來處理

android。app。Fragment

的歷史遺留程式碼之外,不推薦使用。

3。 基於 ViewModel 通訊

ViewModel 是目前使用最廣泛的通訊方式之一,在 Kotlin 中使用時,需要引入

fragment-ktx

class ListViewModel : ViewModel() { private val originalList: LiveData>() = 。。。 val itemList: LiveData> = 。。。 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:

4。 基於 Resutl 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)}

上面的程式碼可以用下圖表示:

一道面試題:介紹一下 Fragment 間的通訊方式?

Result API的原理非常簡單,FragmentA 透過 Key 向 FragmentManager 註冊

ResultListener

,FragmentB 返回 result 時, FM 透過 Key 將結果回撥給FragmentA 。需要特別注意的是隻有當 FragmentB 返回時,result才會被真正回傳,如果

setFragmentResult

多次,則只會保留最後一次結果。

生命週期可感知

透過梳理原始碼可以知道Result API是LifecycleAware的

原始碼基於 androidx。fragment:fragment:1。3。0

setFragmentResultListener 實現:

//FragmentManager。javaprivate final Map mResultListeners = Collections。synchronizedMap(new HashMap());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

setFragmentResult 實現:

private final Map mResults = Collections。synchronizedMap(new HashMap()); 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

中清除

mResults 的儲存

//FragmentManager。javavoid restoreSaveState(@Nullable Parcelable state) { //。。。 ArrayList savedResultKeys = fms。mResultKeys; if (savedResultKeys != null) { for (int i = 0; i < savedResultKeys。size(); i++) { mResults。put(savedResultKeys。get(i), fms。mResults。get(i)); } }}

mResults 的恢復

Parcelable saveAllState() { // FragmentManagerState implements Parcelable FragmentManagerState fms = new FragmentManagerState(); //。。。 fms。mResultKeys。addAll(mResults。keySet()); fms。mResults。addAll(mResults。values()); //。。。 return fms;}

如何選擇?Result API 與 ViewModel

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 等進行響應式通訊。

5。 跨Activity的通訊

最後看一下,跨越不同 Activity 的 Fragmnet 間的通訊

跨 Activity 的通訊主要有兩種方式:

startActivityResult

Activity Result API

startActivityResult

Result API出現之前,需要透過

startActivityResult

完成通訊,這也是

android。app。Fragment

唯一可選的方式。

一道面試題:介紹一下 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

Result API

1。3。0-alpha02

起,Fragment 支援

registerForActivityResult()

的使用,透過 Activity 的 ResultAPI 實現跨 Activity 通訊。

FragmentA 設定回撥:

class FragmentA : Fragment() { private val startActivityLauncher: ActivityResultLauncher = 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 # prepareCallInternalreturn new ActivityResultLauncher() { @Override public void launch(I input, @Nullable ActivityOptionsCompat options) { ActivityResultLauncher 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 # prepareCallInternalregisterOnPreAttachListener(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 ActivityResultLauncher registerForActivityResult( @NonNull final ActivityResultContract contract, @NonNull final ActivityResultCallback callback) { return prepareCallInternal(contract, new Function() { @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

這類工具進行通訊。

最後

我還整理整理一些Android 開發相關的學習文件、面試題,希望能幫助到大家學習提升,如有需要參考的可以直接私信“1”哦

一道面試題:介紹一下 Fragment 間的通訊方式?

一道面試題:介紹一下 Fragment 間的通訊方式?