前言
最近发现项目中出现这个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);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
后来查看源码addFragment()时候进行ArraList.Contains(xxFragment)判断,基本上可以排除前后台,多线程。为什么这么说,抛出异常的前提条件是同一个Fragmnet对象被重复添加两次导致.
2. FragmentTransaction的commit()和commitAllowingStateLoss()的区别,在我们项目中使用commitAllowingStateLoss()怀疑是使用commitAllowingStateLoss()导致一些状态丢失,使isAdded()和 getSupportFragmentManager().findFragmentByTag(“TAG” + tagPage)判断不准确。
分析:在activity中添加入了如下代码
@Override
protected void onSaveInstanceState(Bundle outState) {
if (outState != null) {
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();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
上面还有一个boolen 值的isFristCreated 成员变量,每次fragment new新对象 为true,否则为false。
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;
}
break;
.......
.......
.......
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
这样写的原因保证已有的条件成立,逻辑不出问题。并且符合规范。发版验证
经过验证可以解决该问题
ft.commitAllowingStateLoss()
getSupportFragmentManager().executePendingTransactions()。
引用:
方式一 http://blog.youkuaiyun.com/leeo1010/article/details/37934987
方式三 http://blog.youkuaiyun.com/stoppig/article/details/31776607