ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

从源码角度理解Can not perform this action after onSaveInstanceState异常

2021-01-18 18:32:49  阅读:764  来源: 互联网

标签:perform 方法 mStateSaved 源码 Override commit onSaveInstanceState super


在开发中经常遇到Fatal Exception: java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState异常,那这个异常出现原因是什么呢,怎么解决呢?

问题描述
出现Fatal Exception: java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState异常有两种情况:

  1. FragmentTransaction的commit()时出现:
    具体堆栈信息如下:

  2. Activity/FragmentActivity的onBackPressed时出现:
    具体堆栈信息如下:

问题原因和解决方法
出现Fatal Exception: java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState异常细分为两种情况,但产生原因是一样的,都是因为在存储状态之后调用了commit()/onBackPressed()方法,在commit()/onBackPrssed()中会调用checkStateLoss()方法,具体如下:

private void checkStateLoss() {
    if (this.mStateSaved) {
      throw new IllegalStateException("Can not perform this action after onSaveInstanceState");
    } else if (this.mNoTransactionsBecause != null) {
      throw new IllegalStateException("Can not perform this action inside of " + this.mNoTransactionsBecause);
    }
  }

其中如果this.mStateSaved为true,就会抛出这个异常。而对于mStateSaved用了保存Fragment状态,是在Activity#onSaveInstanceState时通过调用FragmentManager#saveAllState方法,来进行Fragment的状态保存,同时设置mStateSaved为true,用来标识状态已被保存过。下面具体分析两种情况:
1.FragmentTransaction的commit()时出现:
FragmentTransaction的commit()会调用BackStackRecord.java的commit()方法,可以看到commit()和commitAllowingStateLoss()的具体实现,如下:
BackStackRecord.java

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

    @Override
    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);
            pw.close();
        }
        mCommitted = true;
        if (mAddToBackStack) {
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }

可以看出commit()和commitAllowingStateLoss()都是调用commitInternal()方法,只是传参不一样,而在commitInternal()方法中,根据传参会调用FragmentManager的enqueueAction方法,具体如下:

public void enqueueAction(OpGenerator 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<>();
            }
            mPendingActions.add(action);
            scheduleCommit();
        }
    }

如果allowStateLoss为true的话会调用checkStateLoss()方法进行检测。所以从源码可知commit()和commitAllowingStateLoss()的区别在于,commit()方法会检测fragment的状态,而commitAllowingStateLoss()不会对状态进行检测,状态会丢失。
由此得到结论和解决方法:
(1)在activity的生命周期方法中提交事务要小心,越早越好,比如onCreate或是在接收用户的输入时来提交。尽量避免在onActivityResult()方法中提交。
(2)避免在异步的回调方法中执行commit。因为他们感知不到当前activity生命周期的状态。
(3)如果ui状态的改变对用户来说是可以接受的话使用commitAllowingStateLoss()代替commit()。
2.Activity/FragmentActivity的onBackPressed时出现:
按返回键时会调用super.onBackPressed()方法,如下:
FragmentActivity.java中

@Override
    public void onBackPressed() {
        if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
            super.onBackPressed();
        }
    }

会调用FragmentManagerImpl的popBackStackImmediate()方法,如下:

@Override
    public boolean popBackStackImmediate() {
        checkStateLoss();
        return popBackStackImmediate(null, -1, 0);
    }

会调用checkStateLoss()方法,同上,会检测状态。
那这个问题怎么解决呢?
(1)添加try…catch,感觉这种方法并没有解决根本问题,不推荐。
(2)重写onSaveInstanceState方法,不调用super,但onSaveInstanceState方法是用来存储状态使用的,不调用super,防止系统保存fragment的状态,可能会引发一引起其他的问题;再有就是,对于support包下,在其onStop时也会把mStateSaved置为true,仍然能够遇到state loss exception。不推荐。
(3)设置标志位,状态保存过后,不处理KEY事件。具体如下:

public class FragmentStateLossActivity extends Activity {
    private static final String TAG = "Fragment state loss";
    private boolean mStateSaved;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment_state_loss);
        mStateSaved = false;
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        // Not call super won't help us, still get crash
        super.onSaveInstanceState(outState);
        imStateSaved = true;
    }

    @Override
    protected void onResume() {
        super.onResume();
        mStateSaved = false;
    }

    @Override
    protected void onStop() {
        super.onStop();
        mStateSaved = true;
    }

    @Override
    protected void onStart() {
        super.onStart();
        mStateSaved = false;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (!mStateSaved) {
            return super.onKeyDown(keyCode, event);
        } else {
            // State already saved, so ignore the event
            return true;
        }
    }

    @Override
    public void onBackPressed() {
        if (!mStateSaved) {
            super.onBackPressed();
        }
    }
}

但这种方法虽然能从根本上解决crash,但相对比较麻烦。
目前还没有发现更好的方法。

参考
https://juejin.im/entry/58636864128fe10069efc4b5
https://huxian99.github.io/2016/08/28/cj3qymo360000owxk9zp17alo/

标签:perform,方法,mStateSaved,源码,Override,commit,onSaveInstanceState,super
来源: https://www.cnblogs.com/sishuiliuyun/p/14294353.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有