Can not perform this action afte

作者: Rflyee | 来源:发表于2017-10-25 19:42 被阅读356次

    异常如下:

    Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    
    异常截图

    在使用fragment、dialogFragment等控件时可能会遇到以上异常。原因正如抛出的异常所说一样:不能在onSaveInstanceState之后执行该操作。即不能执行commit()操作,包括dialogFragment的show()、dismiss(),fragment中fragmentTransaction的commit()等等。

    接下来,找一下崩溃源码,追一下崩溃相关的源码流程。

    对应的崩溃源码流程:

    比如调用DialogFragment#show()

    public void show(FragmentManager manager, String tag) {
        mDismissed = false;
        mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commit(); // 这里这里
    }
    

    commit的实现方法:

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

    最终到了FragmentManager中,接着看enqueueAction方法

    public void enqueueAction(Runnable action, boolean allowStateLoss) {
        if (!allowStateLoss) { // 关注一下这个变量,后续会讲到
            checkStateLoss(); // 这里这里
        }
        synchronized (this) {
            if (mDestroyed || mHost == null) {
                throw new IllegalStateException("Activity has been destroyed");
            }
            if (mPendingActions == null) {
                mPendingActions = new ArrayList<Runnable>();
            }
            mPendingActions.add(action);
            if (mPendingActions.size() == 1) {
                mHost.getHandler().removeCallbacks(mExecCommit);
                mHost.getHandler().post(mExecCommit);
            }
        }
    }
    private void checkStateLoss() {
        if (mStateSaved) { // 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时,mStateSaved已经被置为true,则会抛出该异常。

    那么问题来了,都有哪些方法会保存状态,也就是把mStateSaved置为true呢?

    • onSaveInstanceState
      既然异常中说了和onSaveInstanceState方法有关,那么就先看看该方法做了什么:

    Activity.java(android-22)

    /**
     * Called to retrieve per-instance state from an activity before being killed
     * so that the state can be restored in {@link #onCreate} or
     * {@link #onRestoreInstanceState} (the {@link Bundle} populated by this method
     * will be passed to both).
     * ........... 这里省略的说明对理解onSaveInstanceState很重要,有点多,自己去看吧
     */
    protected void onSaveInstanceState(Bundle outState) {
        // 保存window的Hierarchy状态
        outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); 
        // 保存fragment的状态。如果感兴趣可以再看看mFragments.saveAllState()的实现,也是保存状态
        Parcelable p = mFragments.saveAllState(); 
        if (p != null) {
            outState.putParcelable(FRAGMENTS_TAG, p);
        }
        getApplication().dispatchActivitySaveInstanceState(this, outState);
    }
    

    FragmentActivity.java(android-22)

    /**
     * Save all appropriate fragment state.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
        Parcelable p = mFragments.saveAllState();
        if (p != null) {
            outState.putParcelable(FRAGMENTS_TAG, p);
        }
    }
    

    可以继续跟下 mFragments.saveAllState(),

    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; // 在这里保存了
        }
        ....省略....
    }
    

    另外,可以再看看FragmentActivity#onStop()方法:

    @Override
    protected void onStop() {
        super.onStop();
        mStopped = true;
        mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
        mFragments.dispatchStop();
    }
    

    继续追继续追,最后也是到FragmentManager中

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

    是的,yeke直接看FragmentManager中mStateSaved的变化时机就可以了,比如下边几个,更详细的跟下源码即可:

    public void noteStateNotSaved() {
        mStateSaved = false; // 这里
    }
    
    public void dispatchCreate() {
        mStateSaved = false; // 这里
        moveToState(Fragment.CREATED, false);
    }
    
    public void dispatchActivityCreated() {
        mStateSaved = false; // 这里
        moveToState(Fragment.ACTIVITY_CREATED, false);
    }
    
    public void dispatchStart() {
        mStateSaved = false; // 这里
        moveToState(Fragment.STARTED, false);
    }
    
    public void dispatchResume() {
        mStateSaved = false; // 这里
        moveToState(Fragment.RESUMED, false);
    }
    
    public void dispatchPause() {
        moveToState(Fragment.STARTED, false);
    }
    
    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);
    }
    

    目前为止,基本了解了该异常的抛出相关的源码流程了。
    下篇博客这里这里继续分析下如何避免该异常,以及其他相关细节。

    转载请标明来源:http://blog.csdn.net/rflyee/article/details/74719551

    相关文章

      网友评论

        本文标题:Can not perform this action afte

        本文链接:https://www.haomeiwen.com/subject/forcpxtx.html