Android | 玩转fragment,使用commit出现闪退问题解析
在最近的项目过程中,使用了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 {
......
public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}
.... ...
}
我们看到这个实现类中的该方法是返回一个 BackStateRecord 类的实体,我们继续去追踪这个类,就会发现,这个类其实是继承自 FragmentTransaction 的,并且,我们在这里看到我们熟悉的方法:
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.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<Runnable>();
}
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 就是已经保存状态,所以会抛出异常。
到这里就结束啦!