Fragment 重疊(重影)問題

在最近做的項目中摹察,遇到了 Fragment 重疊的問題恩掷。在用Fragment做Tab頁面,發(fā)現(xiàn)有時候進入應用會同時顯示多個Tab內(nèi)容供嚎,UI發(fā)生重疊黄娘。直接back鍵退出應用再進入時,則沒有出現(xiàn)該問題克滴。

后面才知道逼争,當應用被強行關閉后(通過手機管家軟件手動強關,或系統(tǒng)為節(jié)省內(nèi)存自動關閉應用)劝赔,再次進入應用時誓焦,每次都有這現(xiàn)象。正常情況下顯示是對的着帽,現(xiàn)象發(fā)生在我切換到其他的app罩阵,操作一會之后,再回到當前的app启摄,有一定幾率會出現(xiàn) Fragment 重疊的現(xiàn)象稿壁。具體的情況是,app 需要在多個 Fragment 間切換歉备,并且保存每個 Fragment 的狀態(tài)傅是。官方的方法是使用 replace() 來替換 Fragment,但是 replace() 的調(diào)用會導致 Fragment 的 onCreateView() 被調(diào)用,所以切換界面時會無法保存當前的狀態(tài)喧笔。因此一般采用 add()帽驯、hide()與 show()配合,來達到保存 Fragment 的狀態(tài)书闸。

可以通過下面的方式進行手動重現(xiàn)這個BUG:

1.手機的 “設置” - “開發(fā)者選項” - 打開”不保留活動”(主要用于模擬Activity被及時回收)

2.把 app 切換到后臺尼变,再重新打開,通過點按不同的 tab 來切換 Fragment

起初我以為是我在使用add()浆劲、hide() 嫌术、show() 切換 Fragment 的時候有什么地方使用的不對,嘗試去解決重疊的 bug牌借,無果后度气,還是通過 google 找出了原因和解決方案。

原因

使用 Fragment 的狀態(tài)保存膨报,當系統(tǒng)內(nèi)存不足磷籍,F(xiàn)ragment 的宿主 Activity 回收的時候,F(xiàn)ragment 的實例并沒有隨之被回收现柠。Activity 被系統(tǒng)回收時院领,會主動調(diào)用 onSaveInstance() 方法來保存視圖層(View Hierarchy),所以當 Activity 通過導航再次被重建時够吩,之前被實例化過的 Fragment 依然會出現(xiàn)在 Activity 中栅盲,此時的 FragmentTransaction 中的相當于又再次 add 了 fragment 進去的,hide()和show()方法對之前保存的fragment已經(jīng)失效了废恋。綜上這些因素導致了多個Fragment重疊在一起。

通過分析發(fā)現(xiàn)扒寄,正常back鍵退出應用時鱼鼓,Activity及Fragment對象會被銷毀,因此再次進入時會在切換到Tab時創(chuàng)建對應的Fragment對象该编。

但是當強行關閉應用后迄本,Activity雖然被回收,但Fragment對象仍然保持课竣,再次進入應用時嘉赎,系統(tǒng)會分別調(diào)用Fragment的onAttach方法將其附加到Activity上,

這里對應的就是強行關閉應用前的fragment對象于樟,

后面會分別調(diào)用兩個fragment的onCreateView方法公条,因此這兩個Fragment對應的View層次結構都會加到Activity的View層次中。

雖然setSelection方法會把所有fragment先隱藏再顯示選中的對象迂曲,但由于此時Activity中Fragment對象的成員變量還未初始化靶橱,因此會再次實例化fragment對象,

之后add、show及hide的都是在第二次創(chuàng)建的對象上操作的关霸,而之前被保持的fragment對象的視圖層次已經(jīng)反映到Activity視圖中并且不會被hide传黄,因此發(fā)生了上述重疊現(xiàn)象。

解決方法:

方案一:

在Activity的onAttachFragment方法中队寇,有一個fragment參數(shù)膘掰,它就是onAttach方法對應的Fragment對象,

通過判斷這個fragment對象佳遣,如果屬于我們的FragmentTabX類并且該類還未被實例化過识埋,則將Activity的成員變量mFragmentTabX指向該fragment對象,這樣就可以在原來的fragment對象上操作add/show/hide苍日,因此不會有重疊現(xiàn)象

方案二:

Activity 中的 onSaveInstanceState() 里面有一句super.onSaveInstanceState(outState);惭聂,Google 對于這句話的解釋是 “Always call the superclass so it can save the view hierarchy state”,大概意思是“總是執(zhí)行這句代碼來調(diào)用父類去保存視圖層的狀態(tài)”相恃。通過注釋掉這句話辜纲,這樣主 Activity 因為種種原因被回收的時候就不會保存之前的 fragment state,也可以成功解決重疊的問題拦耐。


//解決重疊耕腾,方法1
@Override
protectedvoidonSaveInstanceState(Bundle outState) {
//如果用以下這種做法則不保存狀態(tài),再次進來的話會顯示默認tab
//super.onSaveInstanceState(outState);
}
//解決重疊杀糯,方法2
@Override
publicvoidonAttachFragment(Fragment fragment){
//當前的界面的保存狀態(tài)扫俺,只是重新讓新的Fragment指向了原本未被銷毀的fragment,它就是onAttach方法對應的Fragment對象
if(FragmentA ==null&& fragmentinstanceofFragmentTabA)
{

FragmentA = (FragmentTabA)fragment;

}else if(FragmentB ==null&& fragmentinstanceofFragmentTabB){

FragmentB = (FragmentTabB)fragment;

}else if(FragmentC ==null&& fragmentinstanceofFragmentTabC){

FragmentC = (FragmentTabC)fragment;

}else if(FragmentD ==null&& fragmentinstanceofFragmentTabD){

FragmentD = (FragmentTabD)fragment;

}

}

通過以上也可知固翰,某些情況當系統(tǒng)需要將Activity回收以便節(jié)省內(nèi)存時狼纬,Activity內(nèi)部保持的fragment不會被銷毀,可用于保存/恢復數(shù)據(jù)骂际。

下面是全部的代碼:

package com.mobile.margaret.margaretapplication.activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import com.mobile.margaret.margaretapplication.R;
import com.mobile.margaret.margaretapplication.fragment.FragmentTabA;
import com.mobile.margaret.margaretapplication.fragment.FragmentTabB;
import com.mobile.margaret.margaretapplication.fragment.FragmentTabC;
import com.mobile.margaret.margaretapplication.fragment.FragmentTabD;

/** * @author Liz_Miller 
* @Title: TabActivity_Fragment_RadioGroup
 * @Package com.mobile.margaret.margaretapplication.activity 
* @Description: ${todo}(用一句話描述該文件做什么)
 * @date 2016/5/30 15:07 */

public class TabActivity_Fragment_RadioGroup extends FragmentActivity{ 

private static final String TAG = "Liz_Miller"; 
private FrameLayout mHomeContent;
 private RadioGroup mHomeRadioGroup;
 private RadioButton mHomeA;
 private RadioButton mHomeB; 
private RadioButton mHomeC;
 private RadioButton mHomeD; 
private Fragment FragmentA; 
private Fragment FragmentB; 
private Fragment FragmentC; 
private Fragment FragmentD; 
private int tabIds[] = new int[]{ R.id.id_tab_weixin, R.id.id_tab_frd, R.id.id_tab_address, R.id.id_tab_setting, };

 @Override
 protected void onCreate(Bundle savedInstanceState){
   
     super.onCreate(savedInstanceState);
     Log.d(TAG,"onCreate");     
    getWindow() .setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,               
    WindowManager.LayoutParams.FLAG_FULLSCREEN);   
    requestWindowFeature(Window.FEATURE_NO_TITLE);       
    setContentView(R.layout.layout_fragment_radiogroup_maintab); 
    initViews();
    initEvents();
 }

 //初始化控件
 public void initViews()
{ 
   mHomeContent = (FrameLayout)findViewById(R.id.fragment_content); 
   mHomeRadioGroup = (RadioGroup)findViewById(R.id.mHomeRadioGroup);   
   mHomeA = (RadioButton)findViewById(R.id.id_tab_weixin);
  mHomeB = (RadioButton)findViewById(R.id.id_tab_frd); 
  mHomeC = (RadioButton)findViewById(R.id.id_tab_address);
  mHomeD = (RadioButton)findViewById(R.id.id_tab_setting);
 } 
@Override 
public void onAttachFragment(Fragment fragment) {
    // TODO Auto-generated method stub 
        super.onAttachFragment(fragment);
        Log.d(TAG,"onAttachFragment");
 } 

@Override 
protected void onDestroy() { 
  // TODO Auto-generated method stub
      super.onDestroy(); 
      Log.d(TAG,"onDestroy");
 }
 @Override
 protected void onPause() {
   // TODO Auto-generated method stub
       super.onPause(); 
       Log.d(TAG,"onPause"); 
}

 @Override
 protected void onResume() { 
   // TODO Auto-generated method stub 
       super.onResume();
       Log.d(TAG,"onResume");
 } 

@Override 
protected void onStart() { 
// TODO Auto-generated method stub
    super.onStart();
    Log.d(TAG, "onStart"); 
} 

@Override  
protected void onStop() { 
// TODO Auto-generated method stub
     super.onStop();
    Log.d(TAG, "onStop"); 
} 

public void initEvents()
{
    mHomeRadioGroup.setOnCheckedChangeListener(
    new RadioGroup.OnCheckedChangeListener() 
    {
          @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
                     for (int i = 0; i < tabIds.length; i++) { 
                        if (tabIds[i] == checkedId) {
                               setSelection(i);
                               break; 
                        }
        } 
     } 
  });
 setSelection(0);
} 

private void setSelection(int position){
  FragmentManager fm = getSupportFragmentManager(); 
  FragmentTransaction mfragmentTransaction = fm.beginTransaction();          
  hideAllFragments(mfragmentTransaction);
 switch (position){
      case 0:
          mHomeA.setSelected(true); 
          if (FragmentA == null) {
               FragmentA = new FragmentTabA();          
               mfragmentTransaction.add(R.id.fragment_content, FragmentA);
          }
         else { 
            mfragmentTransaction.show(FragmentA); 
          } 
          break; 
      case 1:
           mHomeB.setSelected(true); 
           if (FragmentB == null) { 
                FragmentB = new FragmentTabB();  
               mfragmentTransaction.add(R.id.fragment_content, FragmentB);
            }
            else {
                 mfragmentTransaction.show(FragmentB); 
             }
             break; 
      case 2: 
          mHomeC.setSelected(true); 
          if (FragmentC == null) {
               FragmentC = new FragmentTabC();       
              mfragmentTransaction.add(R.id.fragment_content, FragmentC);
           }
           else {
               mfragmentTransaction.show(FragmentC);
            } 
            break;
     case 3: 
           mHomeD.setSelected(true);
           if (FragmentD == null) { 
                FragmentD = new FragmentTabD();           
                mfragmentTransaction.add(R.id.fragment_content, FragmentD); 
           }
           else { 
              mfragmentTransaction.show(FragmentD); 
            } 
            break; 
     default: 
          break;
  } 
  mfragmentTransaction.commit();
}

 private void hideAllFragments(FragmentTransaction ft){
       if (FragmentA != null) 
       { 
          ft.hide(FragmentA); 
          mHomeA.setSelected(false); 
       } 
       if (FragmentB != null) 
      {
         ft.hide(FragmentB); 
         mHomeB.setSelected(false);
      } 
      if (FragmentC != null)
      {
          ft.hide(FragmentC);
          mHomeC.setSelected(false); 
      } 
       if (FragmentD != null)
       {
           ft.hide(FragmentD);
           mHomeD.setSelected(false);
       }
 } 

//解決重疊疗琉,方法1
 @Override 
protected void onSaveInstanceState(Bundle outState) { 
         //如果用以下這種做法則不保存狀態(tài),再次進來的話會顯示默認的tab
          // super.onSaveInstanceState(outState); 
}


 //解決重疊歉铝,方法2
 /* @Override
 public void onAttachFragment(Fragment fragment){
 //當前的界面的保存狀態(tài)盈简,只是從新讓新的Fragment指向了原本未被銷毀的fragment,它就是onAttach方法對應的Fragment對象
 if(FragmentA == null && fragment instanceof FragmentTabA)
  { 
        FragmentA = (FragmentTabA)fragment;
  }else if(FragmentB == null && fragment instanceof FragmentTabB)
  {      
       FragmentB = (FragmentTabB)fragment;
 }else if(FragmentC == null && fragment instanceof FragmentTabC)
  { 
      FragmentC = (FragmentTabC)fragment; 
}else if(FragmentD == null && fragment instanceof FragmentTabD)
{  
     FragmentD = (FragmentTabD)fragment;
} 
}
*/
}
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末太示,一起剝皮案震驚了整個濱河市柠贤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌类缤,老刑警劉巖臼勉,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異餐弱,居然都是意外死亡坚俗,警方通過查閱死者的電腦和手機镜盯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猖败,“玉大人速缆,你說我怎么就攤上這事《魑牛” “怎么了艺糜?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長幢尚。 經(jīng)常有香客問我破停,道長,這世上最難降的妖魔是什么尉剩? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任真慢,我火速辦了婚禮,結果婚禮上理茎,老公的妹妹穿的比我還像新娘黑界。我一直安慰自己,他們只是感情好皂林,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布朗鸠。 她就那樣靜靜地躺著,像睡著了一般础倍。 火紅的嫁衣襯著肌膚如雪烛占。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天沟启,我揣著相機與錄音忆家,去河邊找鬼。 笑死德迹,一個胖子當著我的面吹牛芽卿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浦辨,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼沼沈!你這毒婦竟也來了流酬?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤列另,失蹤者是張志新(化名)和其女友劉穎芽腾,沒想到半個月后壮池,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辫樱,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年槐脏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艰躺。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡呻袭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腺兴,到底是詐尸還是另有隱情左电,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布页响,位于F島的核電站篓足,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏闰蚕。R本人自食惡果不足惜栈拖,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望没陡。 院中可真熱鬧涩哟,春花似錦、人聲如沸诗鸭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽强岸。三九已至锻弓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蝌箍,已是汗流浹背青灼。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留妓盲,地道東北人杂拨。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像悯衬,于是被迫代替她去往敵國和親弹沽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容