Fragment全解析系列(二):正確的使用姿勢


Fragment系列文章:
1、Fragment全解析系列(一):那些年踩過的坑
2、Fragment全解析系列(二):正確的使用姿勢
3、Fragment之我的解決方案:Fragmentation

本篇主要介紹一些Fragment使用技巧。


Fragment是可以讓你的app縱享絲滑的設計,如果你的app想在現在基礎上性能大幅度提高,並且占用內存降低,同樣的界面Activity占用內存比Fragment要多,響應速度Fragment比Activty在中低端手機上快了很多,甚至能達到好幾倍!如果你的app當前或以后有移植平板等平台時,可以讓你節省大量時間和精力。


簡陋的目錄
1、一些使用建議
2、add(), show(), hide(), replace()的那點事
3、關於FragmentManager你需要知道的
4、使用ViewPager+Fragment的注意事項
5、Fragment事務,你可能不知道的坑
6、是使用單Activity+多Fragment的架構,還是多模塊Activity+多Fragment的架構?


作為一個穩定的app,從后台且回到前台,一定會在任何情況都能恢復到離開前的頁面,並且保證數據的完整性。

如果你沒看過本系列的第一篇,為了方便后面文章的介紹,先規定一個“術語”,安卓app有一種特殊情況,就是 app運行在后台的時候,系統資源緊張的時候導致把app的資源全部回收(殺死app的進程),這時把app再從后台返回到前台時,app會重啟。這種情況下文簡稱為:“內存重啟”。

一些使用建議

1、對Fragment傳遞數據,建議使用setArguments(Bundle args),而后在onCreate中使用getArguments()取出,在 “內存重啟”前,系統會幫你保存數據,不會造成數據的丟失。和Activity的Intent原理一致。

2、使用newInstance(參數) 創建Fragment對象,優點是調用者只需要關系傳遞的哪些數據,而無需關心傳遞數據的Key是什么。

3、如果你需要在Fragment中用到宿主Activity對象,建議在你的基類Fragment定義一個Activity的全局變量,在onAttach中初始化。原因參考第一篇的“getActivity()空指針”部分,在onCreateView() 內出現getActivity()的代碼 很可能是危險的。

protected Activity mActivity; @Override public void onAttach(Activity activity) { super.onAttach(activity); this.mActivity = activity; }

 

add(), show(), hide(), replace()的那點事


1、區別
show()hide()最終是讓Fragment的View setVisibility(true還是false),不會調用生命周期;

replace()的話會銷毀視圖,即調用onDestoryView、onCreateView等一系列生命周期;

add()replace()不要在同一個階級的FragmentManager里混搭使用。

2、使用場景
如果你有一個很高的概率會再次使用當前的Fragment,建議使用show()hide(),可以提高性能。

在我使用Fragment過程中,大部分情況下都是用show()hide(),而不是replace()

3、onHiddenChanged的回調時機
當使用add()+show(),hide()跳轉新的Fragment時,舊的Fragment回調onHiddenChanged(),不會回調onStop()等生命周期方法,而新的Fragment在創建時是不會毀掉onHiddenChanged(),這點要切記。

4、Fragment重疊問題
使用show()hide()帶來的一個問題就是,如果你不做任何處理,在“內存重啟”后,Fragment會重疊;

有些小伙伴可能就是為了避免Fragment重疊問題,而選擇使用replace(),但是使用show()hide()時,重疊問題是完全可以解決的,有兩種方式解決,詳情參考上一篇

 

關於FragmentManager你需要知道的


1、FragmentManager棧視圖
(1)每個Fragment以及宿主Activity(繼承自FragmentActivity)都會在創建時,初始化一個FragmentManager對象,處理好Fragment嵌套問題的關鍵,就是理清這些不同階級的棧視圖。

下面給出一個簡要的關系圖


棧關系圖.png

(2)對於宿主Activity,getSupportFragmentManager()獲取的FragmentActivity的FragmentManager對象;

對於Fragment,getFragmentManager()是獲取的是父Fragment(如果沒有,則是FragmentActivity)的FragmentManager對象,而getChildFragmentManager()是獲取自己的FragmentManager對象。

2、恢復Fragment時(同時防止Fragment重疊),選擇getFragments()還是findFragmentByTag()

(1)選擇getFragments()
對於一個Activity內的多個Fragment,如果Fragment的關系是“流程”,比如登錄->注冊/忘記密碼->填寫信息->跳轉到主頁Activity。這種情況下,用getFragments()的方式是最合適的,在你的Activity內(更好的方式是在你的所有"流程"基類Activity里),寫下如下代碼:

  @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { List<Fragment> fragments = getSupportFragmentManager().getFragments(); if (fragments != null && fragments.size() > 0) { boolean showFlag = false; FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); for (int i = fragments.size() - 1; i >= 0; i--) { Fragment fragment = fragments.get(i); if (fragment != null) { if (!showFlag) { ft.show(fragments.get(i)); showFlag = true; } else { ft.hide(fragments.get(i)); } } } ft.commit(); } } }

上面恢復Fragment的方式,不僅提高性能,同時避免了Fragment重疊現象,最重要的事,你根本不用關系Activity容器里都有哪些Fragment。

(2)選擇findFragmentByTag()恢復
如果你的Activity的Fragments,不是“流程”關系,而是“同級”關系,比如QQ的主界面,“消息”、“聯系人”、“動態”,這3個Fragment屬於同級關系,用上面的代碼就不合適了,恢復的時候總會恢復最后一個,即“動態Fragment”。
正確的做法是在onSaveInstanceState()內保存當前所在Fragment的tag或者下標,在onCreate()是恢復的時候,隱藏其它2個Fragment。

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity); MsgFragment msgFragment; ContactFragment contactFragment; MeFragment meFragment; if (savedInstanceState != null) { // “內存重啟”時調用 msgFragment = getSupportFragmentManager().findFragmentByTag(msgFragment.getClass().getName); contactFragment = getSupportFragmentManager().findFragmentByTag(contactFragment.getClass().getName); meFragment = getSupportFragmentManager().findFragmentByTag(meFragment.getClass().getName); index = saveInstanceState.getInt(KEY_INDEX); // 根據下標判斷離開前是顯示哪個Fragment, // 這里省略判斷代碼,假設離開前是ConactFragment // 解決重疊問題 getFragmentManager().beginTransaction() .show(contactFragment) .hide(msgFragment) .hide(meFragment) .commit(); }else{ // 正常時 msgFragment = MsgFragment.newInstance(); contactFragment = ContactFragment.newInstance(); meFragment = MeFragment.newInstance(); getFragmentManager().beginTransaction() .add(R.id.container, msgFragment, msgFragment.getClass().getName()) .add(R.id.container, contactFragment, contactFragment.getClass().getName()) .add(R.id,container,meFragment,meFragment.getClass().getName()) .hide(contactFragment) .hide(meFragment) .commit(); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // 保存當前Fragment的下標 outState.putInt(KEY_INDEX, index); }

當然在“同級”關系中,使用getFragments()恢復也是可以的。

 

使用ViewPager+Fragment的注意事項


1、使用ViewPager+Fragment時,切換不同ViewPager頁面,不會回調任何生命周期方法以及onHiddenChanged(),只有setUserVisibleHint(boolean isVisibleToUser)會被回調,所以如果你想進行一些懶加載,需要在這里處理。

2、在給ViewPager綁定FragmentPagerAdapter時,new FragmentPagerAdapter(fragmentManager)的FragmentManager,一定要保證正確,如果ViewPager是Activity內的控件,則傳遞getSupportFragmentManager(),如果是Fragment的控件中,則應該傳遞getChildFragmentManager()。只要記住ViewPager內的Fragments是當前組件的子Fragment這個原則即可。

3、如果使用ViewPager+Fragment,不需要在“內存重啟”的情況下,去恢復的Fragments,有FragmentPagerAdapter的存在,不需要你去做恢復工作。

 

Fragment事務,你可能不知道的坑


1、如果你在使用popBackStackImmdiate()方法后,緊接着直接調用類似如下事務的方法,因為他們運行在消息隊列的問題,還沒來得及出棧就運行事務的方法了,這可能會導致不正常現象。

getSupportFragmentManager().popBackStackImmdiate(); getSupportFragmentManager().beginTransaction() .add(R.id.container, fragment , tag) .hide(currentFragment) .commit;

正確的做法是使用主線程的Handler,將事務放到Runnable里運行。

getSupportFragmentManager().popBackStackImmdiate();
new Handler().post(new Runnable(){ @Override public void run() { // 在這里執行Fragment事務 } });

2、給Fragment設定Fragment轉場動畫時,如果你沒有一整套解決方案,應避免使用.setTransition(transit)以及.setCustomAnimations(enter, exit, popEnter, popExit),而只使用.setCustomAnimations(enter, exit)這個方法。

其它2個方法會在某種情況下的“內存重啟”中會出現BUG。
本系列最后一篇給出了我的解決方案,解決了該問題,有興趣可以自行查看 :)

另外一提:謹慎使用popStackBack(String tag/int id,int flasg)系列的方法,原因在上一篇中已經描述。

 

是使用單Activity+多Fragment的架構,還是多模塊Activity+多Fragment的架構?


單Activity+多Fragment:
一個app僅有一個Activity,界面皆是Frament,Activity作為app容器使用。

優點:性能高,速度最快。參考:新版知乎 、google系app

缺點:邏輯比較復雜,尤其當Fragment之間聯動較多或者嵌套較深時,比較復雜。

多模塊Activity+多Fragment:
一個模塊用一個Activity,比如
1、登錄注冊流程:
LoginActivity + 登錄Fragment + 注冊Fragment + 填寫信息Fragment + 忘記密碼Fragment
2、或者常見的數據展示流程:
DataActivity + 數據列表Fragment + 數據詳情Fragment + ...

優點:速度快,相比較單Activity+多Fragment,更易維護。

我的觀點:
權衡利弊,我認為多模塊Activity+多Fragment是最合適的架構,開發起來不是很復雜,app的性能又很高效。

當然。Fragment只是官方提供的靈活組件,請優先遵從你的項目設計!真的特別復雜的界面,或者單個Activity就可以完成一個流程的界面,使用Activity可能是更好的方案。

最后

如果你讀完了第一篇和這篇文章,那么我相信你使用多模塊Activity+多Fragment的架構所遇到的坑,大部分都應該能找到解決辦法。

但是如果流程較為復雜,比如Fragment A需要啟動一個新的Fragment B並且關閉當前A,或者A啟動B,B在獲取數據后,想在返回到A時把數據交給A(類似Activity的startActivityForResult),又或者你保證在Fragment轉場動畫的情況下,使用pop(tag\id)從棧內退出多個Fragment,或者你甚至想Fragment有一個類似Activity的SingleTask啟動模式,那么你可以參考下一篇,我的解決方案庫,Fragmentation。它甚至提供了一個讓你在開發時,可以隨時查看所有階級的棧視圖的UI界面。



文/YoKeyword(簡書作者)
原文鏈接:http://www.jianshu.com/p/fd71d65f0ec6
著作權歸作者所有,轉載請聯系作者獲得授權,並標注“簡書作者”。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM