1. 程式人生 > >關於 java.lang.IllegalStateException: Fragment already added 解決方式

關於 java.lang.IllegalStateException: Fragment already added 解決方式

前言
最近發現專案中出現這個bug,很頻繁。網上查找了幾種解決方案,效果不是太理想,現就將使用修改方案一一列出來

背景
專案底部四個tab頁面切換導致,tab切換方案是,將四個Fragment新增到一個Activity中進行管理動態hidden(),show(),add()。

####異常:

java.lang.IllegalStateException: Fragment already added: InvestmentFragment{44bb4a10 #1 id=0x7f0900d6 TAG1}
	at android.support.v4.app.FragmentManagerImpl.addFragment(FragmentManager.java:1197)
	at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:673)
	at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1489)
	at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:454)
	at android.os.Handler.handleCallback(Handler.java:733)
	at android.os.Handler.dispatchMessage(Handler.java:95)
	at android.os.Looper.loop(Looper.java:136)
	at android.app.ActivityThread.main(ActivityThread.java:5291)
	at java.lang.reflect.Method.invokeNative(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:515)
	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:849)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:665)
	at dalvik.system.NativeStart.main(Native Method)

###方式一
推測原因 :當快速雙擊呼叫FragmentTransaction.add()方法新增fragmentA,而fragmentA不是每次單獨生成的,就會引起這個異常。DialogFragment.show()內部呼叫了FragmentTransaction.add()方法,所以呼叫DialogFragment.show()方法時候也可能會出現這個異常。

在add()方法時候,先判斷fragmentA.isAdded(),如下呼叫可以避免該異常:

if(!fragmentA.isAdded()){
                    FragmentManager manager = ((FragmentActivity)context).getSupportFragmentManager();
                    FragmentTransaction ft = manager.beginTransaction();
                    ft.add(fragmentA, "fragment_name");
                    ft.commit();
}

經測試上面這種方式,不能解決重複新增Fragment問題,fragmentA.isAdded()不能確定fragment一定added activity。

###方式二 既然一個方法不能控制,那就多加幾個方法 多加一個

getSupportFragmentManager().findFragmentByTag("TAG" + tagPage),通過Tag加上顯示的角標
  if (!f.isAdded() && null == getSupportFragmentManager().findFragmentByTag("TAG" + tagPage){
add(R.id.main_fg_content, f, "TAG" + tagPage);
.......

}
Commit()

測試結果:同樣的異常丟擲,原因是假如快速執行兩次該方法。getSupportFragmentManager().findFragmentByTag(“TAG” + tagPage)這個方法均為null。原因就是commit()方法執行後並沒有立即 add(R.id.main_fg_content, f, “TAG” + tagPage)造成
findFragmentByTag(xxx)讀取不到內容
系統解釋:

Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready.

解決辦法是:呼叫
getSupportFragmentManager.executePendingTransactions()

###方式三
1.由於前後臺切換導致(activity 停留後臺時間過長,被系統回收),狀態還原時候,重現new Framgent 而老的fragent還存在記憶體中待回收 出現問題

系統原始碼:

public void addFragment(Fragment fragment, boolean moveToStateNow) {
    if(this.mAdded == null) {
        this.mAdded = new ArrayList();
    }

    if(DEBUG) {
        Log.v("FragmentManager", "add: " + fragment);
    }

    this.makeActive(fragment);
    if(!fragment.mDetached) {
        if(this.mAdded.contains(fragment)) {
            throw new IllegalStateException("Fragment already added: " + fragment);
        }

        this.mAdded.add(fragment);
        fragment.mAdded = true;
        fragment.mRemoving = false;
        if(fragment.mHasMenu && fragment.mMenuVisible) {
            this.mNeedMenuInvalidate = true;
        }

        if(moveToStateNow) {
            this.moveToState(fragment);
        }
    }

}

後來檢視原始碼addFragment()時候進行ArraList.Contains(xxFragment)判斷,基本上可以排除前後臺,多執行緒。為什麼這麼說,丟擲異常的前提條件是同一個Fragmnet物件被重複新增兩次導致.
2. FragmentTransaction的commit()和commitAllowingStateLoss()的區別,在我們專案中使用commitAllowingStateLoss()懷疑是使用commitAllowingStateLoss()導致一些狀態丟失,使isAdded()和 getSupportFragmentManager().findFragmentByTag(“TAG” + tagPage)判斷不準確。

分析:在activity中新增入瞭如下程式碼

@Override
    protected void onSaveInstanceState(Bundle outState) {
        // super.onSaveInstanceState(outState);不儲存Bundle資料
        if (outState != null) {//存在Bundle資料,去除fragments的狀態儲存,解決Fragme錯亂問題。
            String FRAGMENTS_TAG = "android:support:fragments";
            outState.remove(FRAGMENTS_TAG);
        }
    }

並沒有對狀態進行儲存,也就是不會有影響,即使commit()時候狀態丟失,系統會拋異常 ,而所說的狀態是Fragment 中儲存的已有狀態,比如某個Tab當前選中位置,Btn是否可點選狀態,總不會成員變數值也丟失!!!

造成原因,同一個Fragment物件 add()兩次造成。
解決方案:

private void showFragment(Fragment f, int tagPage) {
 
    FragmentTransaction ft = fm.beginTransaction();
    if (!f.isAdded() && null == getSupportFragmentManager().findFragmentByTag("TAG" + tagPage)
            && isFristCreated) {
        if (showFg != null) {
            ft.hide(showFg).add(R.id.main_fg_content, f, "TAG" + tagPage);
        } else {
            ft.add(R.id.main_fg_content, f, "TAG" + tagPage);
        }
    } else { //已經載入進容器裡去了....
        if (showFg != null) {
            ft.hide(showFg).show(f);
        } else {
            ft.show(f);
        }
    }
    showFg = f;
    if (!isFinishing()) {
        ft.commitAllowingStateLoss();
        getSupportFragmentManager().executePendingTransactions();
    }

上面還有一個boolen 值的isFristCreated 成員變數,每次fragment new新物件 為true,否則為false。

Fragment 點選切換

   ##定義fragment tag 表示符號
    public static final int TAG_MESSAGE = 0; //資訊
    public static final int TAG_CHART = 1;
    public static final int TAG_REBATE = 2;
    public static final int TAG_MINE = 3;//我的

  @Override
    public void onstart() {
        showPage = getIntent().getIntExtra("page", TAG_MINE); //獲取上次快取的fragment 進行展示
        }

   @Override
    public void onClick(View v) {
        super.onClick(v);
        switch (v.getId()) {
               case R.id.main_rb_mine:
                if (mineFg == null) {
                    mineFg = new BMineFragment();
                    isFristCreated = true; //可以不用這個
                } else {
                    isFristCreated = false;//可以不用這個
                }
                  showFragment(  mineFg, TAG_MINE);
                break;
                
                .......
                .......
                .......
                }
}


 @Override
    protected void onDestroy() {
        super.onDestroy();
        getIntent().putExtra("page", showPage);  //快取最後顯示的fragment,下次進入進行優先展示
    }

展示具體fragment 通過呼叫改方法 switchFg(showPage)

  private void switchFg(int page) {
        switch (page) {
            case TAG_MESSAGE:
                findViewById(R.id.tab_message).performClick();
                break;
            case TAG_CHART:
                findViewById(R.id.tab_chart).performClick();
                break;

            case TAG_REBATE:
                findViewById(R.id.tab_rebate).performClick();
                break;

            case TAG_MINE:
                findViewById(R.id.tab_mine).performClick();
                break;
            default:
                break;
        }
    }
    ```




經過umeng 線上驗證可以解決該問題

  

   ft.commitAllowingStateLoss(); # commit()同樣思路解決
    getSupportFragmentManager().executePendingTransactions()。
    



-------


>引用blog:
方式一blog    http://blog.csdn.net/leeo1010/article/details/37934987
>方式三  blog  http://blog.csdn.net/stoppig/article/details/31776607