Android解決多個Fragment切換時布局重新執行個體化問題

來源:互聯網
上載者:User

標籤:



本文借鑒自: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切換時布局重新執行個體化問題

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.