源码分析commitAllowingStateLoss() 和commit()的区别

        之前在使用Fragment的时候偶尔会有这么一个报错,Can not perform this action after onSaveInstanceState,意思为无法再onSaveInstanceState之后执行该操作,这个操作就是指commit(),之前也没怎么在意,后来通过查看源码去了解了一下这个问题,以下是对这个问题的解析及对应解决办法的对比。

        Fragment是我们经常用到的东西,常用的操作有添加(add)、移除(remove)、替换(replace)等,这些操作组成一个集合transaction,我们在通过调用getSupportFragmentManager().beginTransaction()来获取这个FragmentTransaction类的实例来管理这些操作,将他们存进由activity管理的back stack中,这样我们就可以进行fragment变化的回退操作,也可以这样去获取FragmentTransaction类的实例:

FragmentManager  mFragmentManager = getSupportFragmentManager();  
FragmentTransaction  mFragmentTransaction = mFragmentManager.beginTransaction();  

        为什么我们会有这种报错呢,因为我们在使用add(),remove(),replace()等方法将Fragment的变化添加进去,然后在通过commit去提交这些变化(另外,在commit之前可以去调用addToBackState()方法,将这些变化加入到activity管理的back stack中去,这样用户调用返回键就可以回退这些变化了),提交完成之后这些变化就会应用到我们的Fragment中去。但是,这个commit()方法,你只能在avtivity存储他的状态之前调用,也就是onSaveInstanceState(),我们都知道activity有一个保存状态的方法和恢复状态的方法,这个就不详细解释了,在onSaveInstanceState()方法之后去调用commit(),就会抛出我们遇到的这个异常,这是因为在onSaveInstanceState()之后调用commit()方法,这些变化就不会被activity存储,即这些状态会被丢失,但我们可以去用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 {

    ... ...

    @Override
    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) {
            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;
    }

}

        终于找到了我们有用的东西了,这里省略了其他不必要的代码,只留下我们需要用的核心代码,有兴趣可以自己去查看源码,这里还有省略掉我们常用的add、remove、replace等方法,回归正题,看我们要找的两个方法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就是已经保存状态,所以会抛出异常。

        长篇大论终于讲完了,其实回头一看并没有多么复杂,就跟着源码一步一步去找,就会找到我们发生错误的地方,看了源码之后发现,原来并没有多么难,so easy!哈哈

    原文作者:若邪〃
    原文地址: https://blog.csdn.net/freelander_j/article/details/52925745
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞