Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android解決多個Fragment切換時布局重新實例化問題

Android解決多個Fragment切換時布局重新實例化問題

編輯:關於Android編程



 



至於fragment的使用就不多說了,直奔主題,




 

布局文件:



    //導航欄
    

        

        

        

        

        

    

    //內容區域
    <framelayout android:background="#EEE" android:id="@+id/content" android:layout_height="match_parent" android:layout_width="match_parent">


</framelayout>





布局預覽圖:


這裡寫圖片描述
 <喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxiciAvPg0KPGJyIC8+DQo8YnIgLz4NCjxiciAvPg0K0tTHsNC0tuC49mZyYWdtZW50x9C7u8rHvq2zo8q508PV4tbWt723qMfQu7tmcmFnbWVudKO6PGJyIC8+DQombmJzcDs8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> /** * 使用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,代碼如下:

 

@Override
protected 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()空指針
可能你遇到過getActivity()返回null,或者平時運行完好的代碼,在“內存重啟”之後,調用getActivity()的地方卻返回null,報了空指針異常。

大多數情況下的原因:你在調用了getActivity()時,當前的Fragment已經onDetach()了宿主Activity。
比如:你在pop了Fragment之後,該Fragment的異步任務仍然在執行,並且在執行完成後調用了getActivity()方法,這樣就會空指針。

解決辦法:
更”安全”的方法:(對於Fragment已經onDetach這種情況,我們應該避免在這之後再去調用宿主Activity對象,比如取消這些異步任務,但我們的團隊可能會有粗心大意的情況,所以下面給出的這個方案會保證安全)

在Fragment基類裡設置一個Activity mActivity的全局變量,在onAttach(Activity activity)裡賦值,使用mActivity代替getActivity(),保證Fragment即使在onDetach後,仍持有Activity的引用(有引起內存洩露的風險,但是相比空指針閃退,這種做法“安全”些),即:

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

/**
*  如果你用了support 23的庫,上面的方法會提示過時,有強迫症的小伙伴,可以用下面的方法代替
*/
@Override
public void onAttach(Context context) {
    super.onAttach(context);
    this.mActivity = (Activity)context;
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved