目錄
本文是對Fragment的基礎介紹的具體實現和應用的補充,參考了GitHub上的一些Demo和博客分析,用於Android學習總結,若有不足之處,望指正~
01.什麼是內存重啓
- 安卓app有一種特殊情況,就是 app運行在後臺的時候,系統資源緊張的時候導致把app的資源全部回收(殺死app的進程),這時把app再從後臺返回到前臺時,app會重啓。這種情況下文簡稱爲:“內存重啓”。(屏幕旋轉等配置變化也會造成當前Activity重啓,本質與“內存重啓”類似)
- 在系統要把app回收之前,系統會把Activity的狀態保存下來,Activity的FragmentManager負責把Activity中的Fragment保存起來。在“內存重啓”後,Activity的恢復是從棧頂逐步恢復,Fragment會在宿主Activity的onCreate方法調用後緊接着恢復(從onAttach生命週期開始)。
02.Fragment常用方法
- Fragment 的動態添加、刪除等操作都需要藉助於 FragmentTransaction 類來完成,比如上面提到的 commit() 操作,下面是幾種常用的方法:
- add() 系列:添加 Fragment 到 Activity 界面中;
- remove():移除 Activity 中的指定 Fragment;
- replace() 系列:通過內部調用 remove() 和 add() 完成 Fragment 的修改;
- hide() 和 show():隱藏和顯示 Activity 中的 Fragment;
- addToBackStack():添加當前事務到回退棧中,即當按下返回鍵時,界面迴歸到當前事物狀態;
- commit():提交事務,所有通過上述方法對 Fragment 的改動都必須通過調用 commit() 方法完成提交
- replace()和hide()區別
- replace()和hide()都可以動態的在Activity中顯示多個Fragment,並且可以來回靈活的切換,但是它們有很大的區別,replace() 方法不會保留 Fragment 的狀態,也就是說諸如 EditText 內容輸入等用戶操作在 remove() 時會消失;但是hide()卻不會,能完整的保留用戶的處理信息。
- addToBackStack()退棧
- 當用戶按下返回鍵時,如果回退棧中保存有之前的事務,會先執行事務回退,然後再執行Activity的finish()方法 。
- add(), show(), hide(), replace()區別
- 它們之間區別
- show(),hide()最終是讓Fragment的View setVisibility(true還是false),不會調用生命週期;
- replace()的話會銷燬視圖,即調用onDestoryView、onCreateView等一系列生命週期;
- add()和 replace()不要在同一個階級的FragmentManager裏混搭使用。
- 使用場景
- 如果你有一個很高的概率會再次使用當前的Fragment,建議使用show(),hide(),可以提高性能。
- 在我使用Fragment過程中,大部分情況下都是用show(),hide(),而不是replace
- 注意:如果你的app有大量圖片,這時更好的方式可能是replace,配合你的圖片框架在Fragment視圖銷燬時,回收其圖片所佔的內存。
- 它們之間區別
03.onHiddenChanged回調時機
- 當使用add()+show(),hide()跳轉新的Fragment時,舊的Fragment回調onHiddenChanged(),不會回調onStop()等生命週期方法,而新的Fragment在創建時是不會回調onHiddenChanged(),這點要切記。
@Override public void onHiddenChanged(boolean hidden) { super.onHiddenChanged(hidden); if(activity!=null){ if(hidden){ //當該頁面隱藏時 }else { //當頁面展現時 } } }
04.傳遞和接收參數
4.1 最常用的方法
- 對Fragment傳遞數據,建議使用setArguments(Bundle args),而後在onCreate中使用getArguments()取出
- 在 “內存重啓”前,系統會幫你保存數據,不會造成數據的丟失。和Activity的Intent恢復機制類似。
- 使用newInstance(參數) 創建Fragment對象,優點是調用者只需要關係傳遞的哪些數據,而無需關心傳遞數據的Key是什麼。
public static WyNewsFragment newInstance(String param) { WyNewsFragment fragment = new WyNewsFragment(); Bundle args = new Bundle(); args.putString(TYPE, param); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { mType = getArguments().getString(TYPE); } }
4.2 Fragment與Activity之間傳值
- Fragment與Activity之間是如何傳值的?
- 1.Activity向Fragment傳值:
- 步驟:
- 要傳的值,放到bundle對象裏;
- 在Activity中創建該Fragment的對象fragment,通過調用
- fragment.setArguments()傳遞到fragment中;
- 在該Fragment中通過調用getArguments()得到bundle對象,就能得到裏面的值。
- 2.Fragment向Activity傳值:
- 第一種:
- 在Activity中調用getFragmentManager()得到fragmentManager,,調用findFragmentByTag(tag)或者通過findFragmentById(id)
- FragmentManager fragmentManager = getFragmentManager();
- Fragment fragment = fragmentManager.findFragmentByTag(tag);
- 第二種:
- 通過回調的方式,定義一個接口(可以在Fragment類中定義),接口中有一個空的方法,在fragment中需要的時候調用接口的方法,值可以作爲參數放在這個方法中,然後讓Activity實現這個接口,必然會重寫這個方法,這樣值就傳到了Activity中。
- 第一種:
- 1.Activity向Fragment傳值:
4.3 Fragment與Fragment之間傳值
- Fragment與Fragment之間是如何傳值的?
- 第一種:
- 通過findFragmentByTag得到另一個的Fragment的對象,這樣就可以調用另一個的方法了。
- 第二種:
- 通過接口回調的方式。
- 第三種:
- 通過setArguments,getArguments的方式。
- 第一種:
4.4 爲何不構造傳值
- 爲什麼fragment傳遞數據不用構造方法傳遞?
- activity給fragment傳遞數據一般不通過fragment的構造方法來傳遞,會通過setArguments來傳遞,因爲當橫豎屏會調用fragment的空參構造函數,數據丟失。
05.FragmentManager棧視圖
- 簡要的關係圖
- 每個Fragment以及宿主Activity(繼承自FragmentActivity)都會在創建時,初始化一個FragmentManager對象,處理好Fragment嵌套問題的關鍵,就是理清這些不同階級的棧視圖。
- 獲取FragmentManager對象
- 對於宿主Activity,getSupportFragmentManager()獲取的FragmentActivity的FragmentManager對象;
- 對於Fragment,getFragmentManager()是獲取的是父Fragment(如果沒有,則是FragmentActivity)的FragmentManager對象,而getChildFragmentManager()是獲取自己的FragmentManager對象。
06.Fragment之懶加載使用
- Fragment之懶加載使用
- 懶加載,其實也就是延遲加載,就是等到該頁面的UI展示給用戶時,再加載該頁面的數據(從網絡、數據庫等),而不是依靠ViewPager預加載機制提前加載兩三個,甚至更多頁面的數據。這樣可以提高所屬Activity的初始化速度,也可以爲用戶節省流量.而這種懶加載的方式也已經/正在被諸多APP所採用。
- 具體可以參考這篇博客,Android 懶加載優化:https://www.jianshu.com/p/cf1f4104de78
- 使用FragmentPagerAdapter+ViewPager時
- 使用FragmentPagerAdapter+ViewPager時,切換回上一個Fragment頁面時(已經初始化完畢),不會回調任何生命週期方法以及onHiddenChanged(),只有setUserVisibleHint(boolean isVisibleToUser)會被回調,所以如果你想進行一些懶加載,需要在這裏處理。
- 在給ViewPager綁定FragmentPagerAdapter時
- 在給ViewPager綁定FragmentPagerAdapter時,new FragmentPagerAdapter(fragmentManager)的FragmentManager,一定要保證正確,如果ViewPager是Activity內的控件,則傳遞getSupportFragmentManager(),如果是Fragment的控件中,則應該傳遞getChildFragmentManager()。只要記住ViewPager內的Fragments是當前組件的子Fragment這個原則即可。
07.首頁Fragment使用
- 首頁tab對應4個fragment,用法如下所示
private void showFragment(int index) { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); hideFragment(ft); position = index; switch (index) { case FRAGMENT_HOME: /** * 如果Fragment爲空,就新建一個實例 * 如果不爲空,就將它從棧中顯示出來 */ if (homeFragment == null) { homeFragment = BaseFragmentFactory.getInstance().getHomeFragment(); ft.add(R.id.fl_main, homeFragment, HomeFragment.class.getName()); } else { ft.show(homeFragment); } break; case FRAGMENT_MOVIE: if (mMovieFragment == null) { mMovieFragment = BaseFragmentFactory.getInstance().getMovieFragment(); ft.add(R.id.fl_main, mMovieFragment, MovieFragment.class.getName()); } else { ft.show(mMovieFragment); } break; case FRAGMENT_VIDEO: if (videoFragment == null) { videoFragment = BaseFragmentFactory.getInstance().getVideoFragment(); ft.add(R.id.fl_main, videoFragment, VideoFragment.class.getName()); } else { ft.show(videoFragment); } break; case FRAGMENT_ME: if (meFragment == null) { meFragment = BaseFragmentFactory.getInstance().getMeFragment(); ft.add(R.id.fl_main, meFragment, MeFragment.class.getName()); } else { ft.show(meFragment); } break; case FRAGMENT_NEWS: if (newsFragment == null) { newsFragment = BaseFragmentFactory.getInstance().getNewsFragment(); ft.add(R.id.fl_main, newsFragment, MeFragment.class.getName()); } else { ft.show(newsFragment); } break; default: break; } ft.commit(); } private void hideFragment(FragmentTransaction ft) { // 如果不爲空,就先隱藏起來 if (homeFragment != null) { setHide(ft,homeFragment); } if (newsFragment != null) { setHide(ft,newsFragment); } if (mMovieFragment != null) { setHide(ft,mMovieFragment); } if (videoFragment != null) { setHide(ft,videoFragment); } if (meFragment != null) { setHide(ft,meFragment); } } private void setHide(FragmentTransaction ft, Fragment fragment) { if(fragment.isAdded()){ ft.hide(fragment); } }
08.思考Fragment能否不依賴Activity嗎
- Fragment能否不依賴於Activity存在?
- Fragment不能獨立存在,它必須嵌入到activity中,而且Fragment的生命週期直接受所在的activity的影響。
- transaction只是記錄了從一個狀態到另一個狀態的變化過程,即比如從FragmentA替換到FragmentB的過程,當通過函數transaction.addToBackStack(null)將這個事務添加到回退棧,則會記錄這個事務的狀態變化過程,如從FragmentA —>FragmentB,當用戶點擊手機回退鍵時,因爲transaction的狀態變化過程被保存,則可以將事務的狀態變化過程還原,即將FragmentB —> FragmentA.
// Create new fragment and transaction Fragment newFragment = new ExampleFragment(); FragmentTransaction transaction = getFragmentManager().beginTransaction(); // Replace whatever is in the fragment_container view with this fragment, // and add the transaction to the back stack transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // Commit the transaction transaction.commit();
- Fragment不能獨立存在,它必須嵌入到activity中,而且Fragment的生命週期直接受所在的activity的影響。