【Fragment研究系列】Exception: commit already called

本文探讨了在Android开发中遇到的`commit already called`异常,解释了该异常产生的原因,涉及到FragmentTransaction的commit过程,并提供了解决方案。通过分析源码,强调了一个BackStackRecord实例的commit方法只能调用一次。同时提出了关于何时使用support包和非support包组件的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

示例代码段


在使用 Fragment 的过程中,我们通常会对 Fragment 做一些操作,例如下面的代码:

Code segment:
    
    ExampleActivity.java
   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Fragment fragment = new Fragment();
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.frame_layout, fragment);
        fragmentTransaction.commit();
        
        fragmentTransaction.hide(fragment);
        fragmentTransaction.commit();
    }

上面的代码是指将一个 fragment 放入 id 为 R.id.frame_layout 的资源里,然后再将 fragment 隐藏。

运行一下,会抛出下面的错误:



这是一个怎样的异常呢?这个异常又是在哪里抛出的呢?为什么会抛出这样的异常?


Exception:commit already called

java.lang.IllegalStateException 定义:

package java.lang;

/**
 * Thrown when an action is attempted at a time when the VM is not
 * in the correct state.
 */
public class IllegalStateException extends RuntimeException {
	……
}


再来看看这个异常是从哪里抛出的:



为什么会抛出这样的异常呢?

首先来看一下是怎样一步一步从 FragmentManager 到 FragmentTransaction 再到 FragmentTransaction.commit()


FragmentTransaction.commit()


首先,是谁实例化了 FragmentManager

众所周知,抽象类是不能被实例化的,但抽象类的对象可以指向一个由抽象类派生出的非抽象类所实例化的对象。那么,这个被派生出的类又是谁呢?

来看代码:

Code segment:
    
    ExampleActivity.java
   	   ……
        FragmentManager fragmentManager = getFragmentManager();
    	   ……


找到 getFragmentManager() 所在的代码段:
Code segment:

Activity.java
……
    public FragmentManager getFragmentManager() {
        return mFragments;
    }
……

找到 mFragment 声明和实例化的地方:

Code segment:

Activity.java
    ……
    final FragmentManagerImpl mFragments = new FragmentManagerImpl();
    ……


所以,由 FragmentManager 派生出的非抽象类为 FragmentManagerImpl

那么,FragmentManagerImpl 这个类是在哪里定义的呢?

这个类比较难找,自己去翻源码无果后,去搜了一下,然后在 StackOverflow 找到了答案(原帖):

本人找不到是因为这个类是定义在 android 支持包 support-v4 里的 FragmentManager.java(在线源码查看) 中的:

Code segment:

FragmentManager.java
 
package android.support.v4.app;
 
public abstract class FragmentManager {
        ……
}
 
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory {
			……
}
 


 那么接下来,调用的即为在 FragmentManagerImpl 中实现了的方法 

Code segment:
    
    ExampleActivity.java
   	   ……
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

在 FragmentManagerImpl 中:

Code segment:

FragmentManager.java
 
package android.support.v4.app;
 
public abstract class FragmentManager {
       ……
}
 
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory {
}


 

而 BackStackRecord 是 FragmentTransaction 派生出的非抽象类

Code segment:

BackStackRecord.java
 
package android.support.v4.app;
 
final class BackStackRecord extends FragmentTransaction implements
        FragmentManager.BackStackEntry, Runnable {
         ……
	 }

 

最后找到 commit() 及其相关代码段:

Code segment:

BackStackRecord.java
 
package android.support.v4.app;
 
final class BackStackRecord extends FragmentTransaction implements
        FragmentManager.BackStackEntry, Runnable {
        ……
    public BackStackRecord(FragmentManagerImpl manager) {
        mManager = manager;
    }
    ……
    public int commit() {
        return commitInternal(false);
    }
 
    int commitInternal(boolean allowStateLoss) {
        if (mCommitted) throw new IllegalStateException("commit already called");
        if (FragmentManagerImpl.DEBUG) {
            Log.v(TAG, "Commit: " + this);
            LogWriter logw = new LogWriter(TAG);
            PrintWriter pw = new PrintWriter(logw);
            dump("  ", null, pw, null);
        }
        mCommitted = true;
        if (mAddToBackStack) {
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }
    ……
}

从代码中可以看出,一旦 mCommitted 为 true,就会抛出异常,而 mCommitted 在整个类中被设置为 true 的情况只有一种:那就是 mCommitted 最开始被声明之后(默认为 false),commitInternal()  第一次顺序执行到 “mCommited = true;” 。之后再调用 commitInternal, 就只能抛出异常了。即:commit() 这个方法在一个 BackStackRecord 实例里只能被使用一次。


解决方案 


再实例一个不同的 BackStackRecord 就可以了,即:再次调用 FragmentManager.beginTransaction()


总结

1.一个 BakStackRecord 实例的 commit 只能调用一次

2.各类关系图:

(因下面的问题未解决,暂空)

提出问题:什么时候使用 support 包中的内容,什么时候又使用非 support 包中的内容呢?

support-v4 包中也有 FragmentManager,那么 FragmentManagerImpl 继承的是哪个 FragmentManager 呢?



评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值