在最近的项目过程中,使用了fragment,我们常规的用法如下:getSupportFragmentManager().beginTransaction().add(R.id.fragment_content, new TestFragment()).commit();
这样的用法对于很多童鞋而言都是比较熟悉的,但是在测试的过程中我们有时候会报如下错误信息:Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
从错误信息就是说我们不能在调用onSaveInstanceState进行commit操作。网上的解决办法是使用commitAllowingStateLoss替换commit。试了一下确实能解决这个问题,但是为什么呢?这就是写这篇的主要原因,划重点。
下面通过源码去看这两个方法的区别:
首先从我们获取 FragmentTransaction 类的实例开始,
即getSupportFragmentManager() ,源码如下:/*** Return the FragmentManager for interacting with fragments associated* with this activity.*/public FragmentManager getSupportFragmentManager() {return mFragments;}
而这个返回的 mFragments 是一个 FragmentManagerImpl 类 的实例,他继承自FragmentManager 这个类:final FragmentManagerImpl mFragments = new FragmentManagerImpl();
我们在 FragmentManager 这个类中还看到 beginTransaction() 这个抽象方法,打开他的实现类:final class FragmentManagerImpl extends FragmentManager{......@Overridepublic FragmentTransaction beginTransaction(){return new BackStackRecord(this);}.... ...}
我们看到这个实现类中的该方法是返回一个 BackStateRecord 类的实体,我们继续去追踪这个类,就会发现,这个类其实是继承自 FragmentTransaction 的,并且,我们在这里看到我们熟悉的方法:final class BackStackRecord extends FragmentTransaction implementsFragmentManager.BackStackEntry, Runnable{public BackStackRecord(FragmentManagerImpl manager){mManager = manager;}public int commit(){return commitInternal(false);}public int commitAllowingStateLoss(){return commitInternal(true);}int commitInternal(boolean allowStateLoss){if (mCommitted) throw new IllegalStateException("commit already called");if (FragmentManagerImpl.DEBUG) {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;}}
这里省略了其他不必要的代码,只留下我们需要用的核心代码,看我们要找的两个方法 :commit() 和 commitAllowingStateLoss()
他们都调用了:commitInternal(boolean allowStateLoss)
这个方法,只是传入的参数不同,我们去看 commitInternal 方法,该方法首先去判断是否已经 commit ,这个简单,直接跳过,最后他执行的是 FragmentTransactionImpl 类的 enqueueAction 方法,我们继续去追踪这个方法:public void enqueueAction(Runnable action, boolean allowStateLoss){if (!allowStateLoss) {checkStateLoss();}synchronized (this) {if (mActivity == null) {throw new IllegalStateException("Activity has been destroyed");}if (mPendingActions == null) {mPendingActions = new ArrayList();}mPendingActions.add(action);if (mPendingActions.size() == 1) {mActivity.mHandler.removeCallbacks(mExecCommit);mActivity.mHandler.post(mExecCommit);}}}
通过这个方法我们可以看到,他首先去根据:
commit 和 commitAllowingStateLoss
这两个方法传入的参数不同去判断,然后将任务扔进 activity 的线程队列中,这里我们重点去看的是 checkStateLoss() 这个方法:private void checkStateLoss() {if (mStateSaved) {throw new IllegalStateException("Can not perform this action after onSaveInstanceState");}if (mNoTransactionsBecause != null) {throw new IllegalStateException("Can not perform this action inside of " + mNoTransactionsBecause);}}
只有调用 commit 方法才会执行这里,commitAllowingStateLoss 则直接跳过这步,这里我们调用 commit 方法时,系统判断状态(mStateSaved)是否已经保存,如果已经保存,则抛出:Can not perform this action after onSaveInstanceState
异常,这就是我们遇到的问题了,而用 commitAllowingStateLoss 方法则不会这样,这就与我们之前分析的activity去保存状态对应上了,在 activity 保存状态完成之后调用 commit 时,这个 mStateSaved 就是已经保存状态,所以会抛出异常。
到这里就结束啦!