全新的 Fragment 通信方式
作者?|?tech-bus.丹卿
來源 | 程序員巴士
前言
就在前段時間,Google 推出了 Fragment Result API 和 Activity Results API,用來取代之前的 Activity 和 Fragment 之間通信方式的不足,大家可以前往看看都有哪些更新:
https://medium.com/androiddevelopers/now-in-android-17-9d73f7bed7f
通過Fragment Result API進行Fragment間數據傳遞:
發送數據
@Override @NonNull public?final?@Override?void?setFragmentResult(@NonNull?String?requestKey,?@NonNull?Bundle?result)如果 FragmentB 發送數據給 FragmentA,需要在 FragmentA 中注冊 listener,通過 parent FragmentManager 發送數據
數據接收:
@Override @NonNull public?final?@Override?void?setFragmentResultListener(@NonNull?String?requestKey,@NonNull?LifecycleOwner?lifecycleOwner,@NonNull?FragmentResultListener?listener )如果想在 Fragment 中接受數據,可以在 FragmentManager 中注冊一個 FragmentResultListener,參數 requestKey 可以過濾掉 FragmentManager 發送的數據
setFragmentResultListener為給定的requestKey設置了ResultListener。一旦給定的 LifecycleOwner 至少處于 STARTED 狀態, setFragmentResult 使用相同的 requestKey 設置的任何結果都將傳遞給回調。回調將保持活動狀態,直到 LifecycleOwner 達到 DESTROYED 狀態或使用相同的 requestKey 調用 clearFragmentResultListener。
時序圖分析:
可以通過簡化后的時序圖來分析lifecycle狀態和fragment設置監聽的順序:
如果監聽 Fragment 的生命周期,您可以在接收到新數據時安全地更新 UI,因為 view 的創建(onViewCreated() 方法在 onStart() 之前被調用)。
當生命周期處于 LifecycleOwner STARTED 的狀態之前,如果有多個數據傳遞,只會接收到最新的值:
當生命周期處于 LifecycleOwner DESTROYED 時,它將自動移除 listener,如果想手動移除 listener,需要調用 FragmentManager.setFragmentResultListener() 方法,傳遞空的 FragmentResultListener
從時序圖很明顯可以總體分析出流程來,大致是在FragmentManager中注冊listener,依賴于Fragment發送返回的數據。
不同層級關系的Fragment數據傳遞
一般fragment數據傳遞涉及到不同層級間的傳遞,主要分為下面兩種:
父子層級的兩個Fragment數據傳遞
如果在 FragmentA 中接受 FragmentB 發送的數據,FragmentA 是 FragmentB 的父容器, 他們通過 child FragmentManager 進行通信
childFragmentManager.setFragmentResultListener(...)注意 listener必須設置的Fragment需要用到相同的FragmentManager。
相同層級的兩個Fragment數據傳遞
如果在 FragmentA 中接受 FragmentB 發送的數據,FragmentA 和 FragmentB 處于相同的層級,通過 parent FragmentManager 進行通信,FragmentA 必須使用 parent FragmentManager 注冊 listener
parentFragmentManager.setFragmentResultListener(...)源碼解析
不同于之前舊的Target Fragment Api,可以看到這里將監聽器和fragment的lifecycle進行綁定,這樣將帶來以下優點:
在 Fragment 之間傳遞數據,不會持有對方的引用
當生命周期處于 ON_START 時開始處理數據,避免當 Fragment 處于不可預知狀態的時,可能發生未知的問題
當生命周期處于 ON_DESTROY 時,移除監聽
那讓我們更進一步看下,fragment和它的lifecycle是如何進行數據監聽的綁定和解綁的呢:
@Overridepublic?final?void?setFragmentResultListener(@NonNull?final?String?requestKey,@NonNull?final?LifecycleOwner?lifecycleOwner,@NonNull?final?FragmentResultListener?listener)?{final?Lifecycle?lifecycle?=?lifecycleOwner.getLifecycle();//destroyed則直接返回if?(lifecycle.getCurrentState()?==?Lifecycle.State.DESTROYED)?{return;}LifecycleEventObserver?observer?=?new?LifecycleEventObserver()?{@Overridepublic?void?onStateChanged(@NonNull?LifecycleOwner?source,@NonNull?Lifecycle.Event?event)?{//在start的時候進行方法調用if?(event?==?Lifecycle.Event.ON_START)?{//?一旦出于start狀態,檢查存儲結果Bundle?storedResult?=?mResults.get(requestKey);if?(storedResult?!=?null)?{//?如果查詢的結果不為空,則觸發回調listener.onFragmentResult(requestKey,?storedResult);//?清除結果clearFragmentResult(requestKey);}}//destroy則移除監聽if?(event?==?Lifecycle.Event.ON_DESTROY)?{lifecycle.removeObserver(this);mResultListeners.remove(requestKey);}}};可以看到上面代碼做了:
獲取 Lifecycle 去監聽 Fragment 的生命周期的變化
當生命周期處于 ON_START 時開始處理數據,避免當 Fragment 處于不可預知狀態的時,可能發生未知的問題
當生命周期處于 ON_DESTROY 時,移除監聽
當生命周期處于 DESTROYED 則直接返回不作處理
看過接受數據如何做的,下面再看下如何發送數據的:
@Overridepublic?final?void?setFragmentResult(@NonNull?String?requestKey,?@NonNull?Bundle?result)?{//?檢查是否有監聽器去監聽requestkey結果FragmentManager.LifecycleAwareResultListener?resultListener?=?mResultListeners.get(requestKey);//?如果生命周期started,則觸發回調if?(resultListener?!=?null?&&?resultListener.isAtLeast(Lifecycle.State.STARTED))?{resultListener.onFragmentResult(requestKey,?result);}?else?{//否則?保存當前傳輸數據resultmResults.put(requestKey,?result);}}獲取 requestKey 注冊的 listener
當生命周期處于 STARTED 狀態時,開始發送數據
否則保存當前傳輸的數據
看完源碼簡單分享,那么再來看下fragment間通信還有哪些其他方法?
Fragment中的通信方式還有哪些
通過使用findFragmentById或關聯Activity獲取Fragment的實例,然后調用Fragment的公共方法:
第一步在被調用的MainFragment注冊公共方法
第二步 在主動調用的Fragment中關聯activity并獲取到MainFragment后,調用公共方法
缺點: Fragment 之間不應該直接通信 參考 https://developer.android.com/topic/libraries/architecture/viewmodel#sharing
接口回調的方式進行fragment間數據傳遞:
step1: 在Menuragment中創建一個接口以及接口對應的set方法:
step2: 在MenuFragment中的ListView條目點擊事件中進行接口進行接口回調
step3: 在MainActivity中根據menuFragment獲取到接口的set方法,在這個方法中進行進行數據傳遞,具體如下:
缺點: 相對Result API更復雜,具體可以參考官方文檔 https://developer.android.com/training/basics/fragments/communicating
通過Target Fragment APIs (Fragment.setTargetFragment() & Fragment.getTargetFragment())方法進行Fragment間數據傳遞:
援引Google官方的說明:
Fragment.setTargetFragment()
Use case = 2 fragments hosted by the same activity.
Where startActivityForResult() establishes a relationship between 2 activities, setTargetFragment() defines the caller/called relationship between 2 fragments.
setTargetFragment(target) lets the "called" fragment know where to send the result. onActivityResult() is called manually in this case.
通過下面的偽代碼可以表示出調用關系:
public?class?Caller?extends?FragmentFragment?called?=?Called.newInstance()called.setTargetFragment(this)public?class?Called?extends?DialogFragmentintent?=?amazingDatagetTargetFragment().onActivityResult(??getTargetRequestCode(),,intent??)簡而言之,假設Fragment A 跳轉B ?在B中做一些操作之后,想把這些操作回傳給A Fragment中存在startActivityForResult()以及onActivityResult()方法,但沒有setResult()方法,用于設置返回的intent,這樣我們就需要通過調用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);
這種方法無形當中增加了兩個Fragment 與 ?Activity的耦合度 所以,就有了setTargetFragment()方法 在啟動B的時候,可以調用 B.setTargetFragment(A,int t) 將AB關聯起來, 然后在B的代碼中調用 getTargetFragment().onActivityResult()將數據回傳給 A
缺點: 該方法目前已經被谷歌官方所廢棄,Target fragment 需要直接訪問另一個fragment 的實例,這是十分危險的,因為不知道目標fragment處于什么狀態;
通過ViewModel容器進行Fragment間數據傳遞:
Activity 中的兩個或更多 Fragment 需要相互通信是一種很常見的現象。想象一下拆分視圖 (list-detail) Fragment 的常見情況,假設您有一個 Fragment,在該 Fragment 中,用戶從列表中選擇一項,還有另一個 Fragment,用于顯示選定項的內容。這種情況不太容易處理,因為這兩個 Fragment 都需要定義某種接口描述,并且所有者 Activity 必須將兩者綁定在一起。此外,這兩個 Fragment 都必須處理另一個 Fragment 尚未創建或不可見的情況。
可以使用 ViewModel 對象解決這一常見的難點。這兩個 fragment 可以使用其 activity 范圍共享 ViewModel 來處理此類通信,如以下示例代碼所示:
public?class?SharedViewModel?extends?ViewModel?{private?final?MutableLiveData<Item>?selected?=?new?MutableLiveData<Item>();public?void?select(Item?item)?{selected.setValue(item);}public?LiveData<Item>?getSelected()?{return?selected;} }public?class?ListFragment?extends?Fragment?{private?SharedViewModel?model;public?void?onViewCreated(@NonNull?View?view,?Bundle?savedInstanceState)?{super.onViewCreated(view,?savedInstanceState);model?=?new?ViewModelProvider(requireActivity()).get(SharedViewModel.class);itemSelector.setOnClickListener(item?->?{model.select(item);});} }public?class?DetailFragment?extends?Fragment?{public?void?onViewCreated(@NonNull?View?view,?Bundle?savedInstanceState)?{super.onViewCreated(view,?savedInstanceState);SharedViewModel?model?=?new?ViewModelProvider(requireActivity()).get(SharedViewModel.class);model.getSelected().observe(getViewLifecycleOwner(),?item?->?{//?Update?the?UI.});} }請注意,這兩個 Fragment 都會檢索包含它們的 Activity。這樣,當這兩個 Fragment 各自獲取 ViewModelProvider 時,它們會收到相同的 SharedViewModel 實例(其范圍限定為該 Activity)。
此方法具有以下優勢:Activity 不需要執行任何操作,也不需要對此通信有任何了解。除了 SharedViewModel 約定之外,Fragment 不需要相互了解。如果其中一個 Fragment 消失,另一個 Fragment 將繼續照常工作。每個 Fragment 都有自己的生命周期,而不受另一個 Fragment 的生命周期的影響。如果一個 Fragment 替換另一個 Fragment,界面將繼續工作而沒有任何問題。
總結
雖然使用了 Fragment result APIs,替換了過時的 Fragment target APIs,但是新的 APIs 在Bundle 作為數據傳傳遞方面有一些限制,只能傳遞簡單數據類型、Serializable 和 Parcelable 數據,Fragment result APIs 允許程序從崩潰中恢復數據,而且不會持有對方的引用,避免當 Fragment 處于不可預知狀態的時,可能發生未知的問題。
綜合以上通信方式,那么你認為 Fragment 之間通信最好的方式是什么?
往期推薦
對數據“投入”卻沒有“產出”?聽聽Gartner的分析
長跑11年,騰訊開源的變與不變
邊緣應用增長800%,聽聽Akamai邊緣部署的經驗
CSDN云原生Meet up深圳站與你不見不散!
點分享
點收藏
點點贊
點在看
總結
以上是生活随笔為你收集整理的全新的 Fragment 通信方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 库克“一语成谶”:又有 30 万台安卓设
- 下一篇: 2017双11技术揭秘—千亿级流量来袭,