关于 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 线上验证可以解决该问题

  

```java
       ft.commitAllowingStateLoss(); # commit()同样思路解决
        getSupportFragmentManager().executePendingTransactions()

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

评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灯塔@kuaidao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值