Can not perform this action after onSaveInstanceStat的一些思考

具体的异常信息

今天在bugly上看到一个bug:IllegalStateException,在此记下对该bug的一些思考。
首先追查Exception信息:
java.lang.IllegalStateException
Can not perform this action after onSaveInstanceState
android.support.v4.app.u.v(FragmentManager.java:1377)
android.support.v4.app.u.c(FragmentManager.java:504)
android.support.v4.app.FragmentActivity.onBackPressed(FragmentActivity.java:178)

然后很明显可以看到问题出现在v4包的FragmentManager.java中,去FragmentManager.java中看下

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);
        }
    }

很容易看到在checkStateLoss()抛出的异常,然后再继续往前找,然后看到BackStackState.java中enqueueAction()中调用了,或许很郁闷BackStackState是什么?
在FragmentManagerImpl.java中有这么一个方法

@Override
    public FragmentTransaction beginTransaction() {
        return new BackStackRecord(this);
    }

经常使用到fragment的小伙伴应该知道这是什么了吧

 public void enqueueAction(Runnable action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            checkStateLoss();
        }
        。。。
    }

接着往前看,然后在BackStackRecord.java中

public int commit() {
        return commitInternal(false);
    }

    public int commitAllowingStateLoss() {
        return commitInternal(true);
    }

    int commitInternal(boolean allowStateLoss) {
        。。。
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }

看到这应该很明显了吧,commit()与commitAllowingStateLoss()两个方法均调用了commitInternal方法,不同的是参数不同,因此使用commit的时候enqueueAction函数中调用了checkStateLoss(),然后当mStateSaved==true时就抛出了该异常。可能你还说那我们使用commitAllowingStateLoss()就不会抛异常了呗,真简单。。可是我觉得使用commitAllowingStateLoss只能说是把错误遮住而并没有去解决!

为什么会产生?

既然知道是因为调用commit()时checkStateLoss中mStateSaved==true抛出,那何时mStateSaved==true呢?其实从变量命名也应该猜出作用了:
public void dispatchStop() {
        // See saveAllState() for the explanation of this.  We do this for
        // all platform versions, to keep our behavior more consistent between
        // them.
        mStateSaved = true;

        moveToState(Fragment.STOPPED, false);
    }

Parcelable saveAllState() {
        // Make sure all pending operations have now been executed to get
        // our state update-to-date.
        execPendingActions();

        if (HONEYCOMB) {
            // As of Honeycomb, we save state after pausing.  Prior to that
            // it is before pausing.  With fragments this is an issue, since
            // there are many things you may do after pausing but before
            // stopping that change the fragment state.  For those older
            // devices, we will not at this point say that we have saved
            // the state, so we will allow them to continue doing fragment
            // transactions.  This retains the same semantics as Honeycomb,
            // though you do have the risk of losing the very most recent state
            // if the process is killed...  we'll live with that.
            mStateSaved = true;
        }
    。。。
    }

这里为什么会出现两个不同的地方使mStateSaved=true,因为3.0版本是一个分界线。在3.0之前,onSaveInstanceState()在onPause之前调用,而在3.0之后onSaveInstanceState() 在onStop()之前调用,因此之前的版本也更容易抛出异常。 到这里我们也明白这个异常产生的原因:我们在activity的状态被保存后仍然去commit一个FragmentTransaction。
因为我们的activity不是一直存在的,当我们的系统内存不够的时候,我们后台的activity可能被系统kill。但是系统允许activity被kill之前用onSaveInstanceState() 用一个bundle保存dialog,fragment,view等的状态信息。然后重建Activity时又利用相应的信息。那么,为什么异常会抛出呢?bundle中存储的只是当onSaveInstanceState() 调用那一时刻的Activity信息,所以当你在onSaveInstanceState()之后调用FragmentTransaction#commit() 时, transaction并没有被当做Activity的信息进行保存,也就是transaction丢失了,导致意外的UI状态丢了,因此抛出了IllegalStateException异常。

那么如何解决呢

  1. 在activity生命周期中调用commit时足够谨慎。在oncreate()中或许不容易出问题,但是涉及到与其他activity的交互时,例如onActivityResult(), onStart(), 和onResume()等方法就很容易出问问题,因为这个时候很有可能Activity还未加载完全。如果应用需要commit那么可以将其放在onPostResume(),如果是FragmentActivity还可以放在onResumeFragments()中,避免放在其它生命周期中。
/**
     * This is the fragment-orientated version of {@link #onResume()} that you
     * can override to perform operations in the Activity at the same point
     * where its fragments are resumed.  Be sure to always call through to
     * the super-class.
     */
    protected void onResumeFragments() {
        mFragments.dispatchResume();
    }
/**
     * Called when activity resume is complete (after {@link #onResume} has
     * been called).
     */
    protected void onPostResume() {
        。。。
    }

2 避免异步调用,例如在AsyncTask的postExecute中调用commit。此时调用很有可能activity还未重建成功。我认为可以在postExecute中给一个标志,然后在onPostResume中判断执行。
3. 最后无奈的方法:commitAllowingStateLoss()。只是用与于避免抛出异常,其实质并没有解决问题。

最后

这是写的第一篇博客,写的有点渣。。
要学的知识还很多,慢慢积累。。。

你可能感兴趣的:(android)