標籤:
本文借鑒自:http://www.jianshu.com/p/d9143a92ad94
至於fragment的使用就不多說了,直奔主題,
布局檔案:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="fan.fragmentdemo.MainActivity"> //導覽列 <LinearLayout android:layout_width="match_parent" android:layout_height="48dp" android:orientation="horizontal"> <TextView android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" android:text="第一個" android:id="@+id/tv_one" android:gravity="center" /> <TextView android:layout_width="1dp" android:layout_height="match_parent" android:background="#EEE" /> <TextView android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" android:text="第二個" android:id="@+id/tv_two" android:gravity="center" /> <TextView android:layout_width="1dp" android:layout_height="match_parent" android:background="#EEE" /> <TextView android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" android:text="第三個" android:id="@+id/tv_three" android:gravity="center" /> </LinearLayout> //內容地區 <FrameLayout android:id="@+id/content" android:background="#EEE" android:layout_width="match_parent" android:layout_height="match_parent" /></LinearLayout>
版面配置預覽圖:
以前寫多個fragment切換是經常使用這種方法切換fragment:
/** * 使用replace切換頁面 * 顯示fragment */ private void showFragment(Fragment fg){ FragmentTransaction transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.content, fg); transaction.commit(); }
replace():該方法只是在上一個Fragment不再需要時採用的簡便方法,弊端就是如果需要重複使用該fragment時,需要每次都要重新載入一次。比如我在第一個fragment輸入資訊後,切換第二個fragment後再切換回去,就會造成資料丟失,如下:
而且,如果每切換一次就執行個體化一次的話,FragmentManager管理下的棧也會爆滿,最終會導致手機卡頓,這很明顯不是正確的Fragment使用姿勢,這時,我們就需要使用show()、hide()、add()了,正確的切換方式是add(),切換時hide(),add()另一個Fragment;再次切換時,只需hide()當前,show()另一個就行了,代碼修改如下:
/** * 使用show() hide()切換頁面 * 顯示fragment */ private void showFragment(Fragment fg){ FragmentTransaction transaction = fragmentManager.beginTransaction(); //如果之前沒有添加過 if(!fg.isAdded()){ transaction .hide(currentFragment) .add(R.id.content,fg); }else{ transaction .hide(currentFragment) .show(fg); } //全域變數,記錄當前顯示的fragment currentFragment = fg; transaction.commit(); }
效果:
有,可以看出,即使切換到別的fragment,再切換回來,資料還依然存在,這就避免了Fragment切換時布局重新執行個體化。
安卓app有一種特殊情況,就是 app運行在背景時候,系統資源緊張的時候導致把app的資源全部回收(殺死app的進程),這時把app再從後台返回到前台時,app會重啟。這種情況下文簡稱為:“記憶體重啟”。(旋轉螢幕等配置變化也會造成當前Activity重啟,本質與“記憶體重啟”類似)
在系統要把app回收之前,系統會把Activity的狀態儲存下來,Activity的FragmentManager負責把Activity中的Fragment儲存起來。在“記憶體重啟”後,Activity的恢複是從棧頂逐步恢複,Fragment會在宿主Activity的onCreate方法調用後緊接著恢複
當我們不退出軟體,只是後台掛著去幹別的事,當系統記憶體不足回收我們這個app時,再切換回來,app的這幾個Fragment介面會重疊。,如:
由可以看出,三個fragment全部疊在了一起,而且點擊上面菜單也不能消除重疊。顯然,這並不是我們想要的,沒事,繼續解決問題,使用findFragmentByTag:即在add()或者replace()時綁定一個tag,一般我們是用fragment的類名作為tag,然後在發生“記憶體重啟”時,通過findFragmentByTag找到對應的Fragment,並hide()需要隱藏的fragment。,修改如下:
/** * 使用show() hide()切換頁面 * 顯示fragment */ private void showFragment(Fragment fg){ FragmentTransaction transaction = fragmentManager.beginTransaction(); //如果之前沒有添加過 if(!fg.isAdded()){ transaction .hide(currentFragment) .add(R.id.content,fg,fg.getClass().getName()); //第三個參數為當前的fragment綁定一個tag,tag為當前綁定fragment的類名 }else{ transaction .hide(currentFragment) .show(fg); } currentFragment = fg; transaction.commit(); }
別急,還沒完,在當前Activity的onCreate()方法裡面添加一下代碼:
if (savedInstanceState != null) { // “記憶體重啟”時調用 //從fragmentManager裡面找到fragment fgOne = (OneFragment) fragmentManager.findFragmentByTag(OneFragment.class.getName()); fgTwo = (TwoFragment) fragmentManager.findFragmentByTag(TwoFragment.class.getName()); fgThree = (ThreeFragment) fragmentManager.findFragmentByTag(ThreeFragment.class.getName()); //解決重疊問題show裡面可以指定恢複的頁面 fragmentManager.beginTransaction() .show(fgOne) .hide(fgTwo) .hide(fgThree) .commit(); //把當前顯示的fragment記錄下來 currentFragment = fgOne;}else{ //正常啟動時調用 fgOne = new OneFragment(); fgTwo = new TwoFragment(); fgThree = new ThreeFragment(); showFragment(fgOne);}
OK,當app後台時遇到“記憶體重啟”的情況下,再返回我們的app,就會恢複到show(fgOne)頁面,而且還不會造成重疊問題!
很顯然,這樣結束是不道德的,因為有人會問了,如果想記錄當前退出的狀態以至於下次恢複時直接顯示之前的fragment頁面怎麼辦,恩,對於這個問題,我們可以在activity的onSaveInstanceState()方法中記錄一下“記憶體重啟”之前的Fragment的頁面,然後在oncreate()中取出來,根據儲存的頁面來顯示到指定的fragment,代碼如下:
@Overrideprotected void onSaveInstanceState(Bundle outState) { //“記憶體重啟”時儲存當前的fragment名字 outState.putString(STATE_FRAGMENT_SHOW,currentFragment.getClass().getName()); super.onSaveInstanceState(outState);}
然後在oncreate()方法中添加(修改上面的那個代碼)
if (savedInstanceState != null) { // “記憶體重啟”時調用 //擷取“記憶體重啟”時儲存的fragment名字 String saveName = savedInstanceState.getString(STATE_FRAGMENT_SHOW); //從fragmentManager裡面找到fragment fgOne = (OneFragment) fragmentManager.findFragmentByTag(OneFragment.class.getName()); fgTwo = (TwoFragment) fragmentManager.findFragmentByTag(TwoFragment.class.getName()); fgThree = (ThreeFragment) fragmentManager.findFragmentByTag(ThreeFragment.class.getName()); //如果為空白就預設操作 if(TextUtils.isEmpty(saveName)){ //解決重疊問題 fragmentManager.beginTransaction() .show(fgOne) .hide(fgTwo) .hide(fgThree) .commit(); //把當前顯示的fragment記錄下來 currentFragment = fgOne; }else{ if(saveName.equals(fgOne.getClass().getName())){ //如果推出之前是OneFragment //解決重疊問題 fragmentManager.beginTransaction() .show(fgOne) .hide(fgTwo) .hide(fgThree) .commit(); //把當前顯示的fragment記錄下來 currentFragment = fgOne; }else if(saveName.equals(fgTwo.getClass().getName())){ //如果推出之前是TwoFragment //解決重疊問題 fragmentManager.beginTransaction() .show(fgTwo) .hide(fgOne) .hide(fgThree) .commit(); //把當前顯示的fragment記錄下來 currentFragment = fgTwo; }else{ //如果推出之前是ThreeFragment //解決重疊問題 fragmentManager.beginTransaction() .show(fgThree) .hide(fgTwo) .hide(fgOne) .commit(); //把當前顯示的fragment記錄下來 currentFragment = fgThree; } }}else{ //正常啟動時調用 fgOne = new OneFragment(); fgTwo = new TwoFragment(); fgThree = new ThreeFragment(); showFragment(fgOne);}
OK,這樣就可以了,我們通過儲存當前顯示的fragment的類名,當我們在第二個fragment頁面時後台,等到“記憶體重啟”後返回該app時,就根據之前儲存的類名來判斷載入指定的fragment,而且,重疊的問題也解決了!
本章代碼及apk:點擊免費下載
最後在說一點:
getActivity()null 指標
可能你遇到過getActivity()返回null,或者平時運行完好的代碼,在“記憶體重啟”之後,調用getActivity()的地方卻返回null,報了null 指標異常。
大多數情況下的原因:你在調用了getActivity()時,當前的Fragment已經onDetach()了宿主Activity。
比如:你在pop了Fragment之後,該Fragment的非同步任務仍然在執行,並且在執行完成後調用了getActivity()方法,這樣就會null 指標。
解決辦法:
更”安全”的方法:(對於Fragment已經onDetach這種情況,我們應該避免在這之後再去調用宿主Activity對象,比如取消這些非同步任務,但我們的團隊可能會有粗心大意的情況,所以下面給出的這個方案會保證安全)
在Fragment基類裡設定一個Activity mActivity的全域變數,在onAttach(Activity activity)裡賦值,使用mActivity代替getActivity(),保證Fragment即使在onDetach後,仍持有Activity的引用(有引起記憶體泄露的風險,但是相比null 指標閃退,這種做法“安全”些),即:
protected Activity mActivity;@Overridepublic void onAttach(Activity activity) { super.onAttach(activity); this.mActivity = activity;}/*** 如果你用了support 23的庫,上面的方法會提示過時,有強迫症的小夥伴,可以用下面的方法代替*/@Overridepublic void onAttach(Context context) { super.onAttach(context); this.mActivity = (Activity)context;}
Android解決多個Fragment切換時布局重新執行個體化問題