StackOverflow
的一个问题:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
解释为什么会抛出这个异常,并且给出解决这个问题的几个建议
为什么抛出这个异常
抛出这个异常的原因是试图在activity
的状态已经保存之后提交FragmentTransaction
,导致activity
状态丢失. 在了解详情之前先来看一看在调用onSaveInstanceState()
都做了什么,在Binders & Death Recipients
文章中讨论了Android
应用运行时不好控制,android
系统通过杀死进程释放内存,后台运行的activity
可能在毫无征兆的情况下被杀死. 为了确保用户的难以预测的行为被隐藏,在activity
被销毁前调用onSaveInstanceState()
方法保存activity
的状态. 不管当前的activity
有没有被系统杀死,用户能在前台和后台的activity
中无缝连接
调用onSaveInstanceState()
时,通过Bundle
保存activity
的状态,activity
的记录了dialogs
,fragments
,views
状态,系统通过代理bundle
与服务器端的接口交互,安全的保存了当前的状态,当系统之后决定恢复activity
,它把相同的bundle
对象返回给应用,通过bundle
恢复之前activity
的状态
那么为什么会抛出异常呢?问题源于这个bundle
对象在onSaveInstanceState()
被调用时记录了当前的activity
,也就意味着当前在onSaveInstanceState()
调用之后调用FragmentTransaction#commit()
,这个处理没有被记住,没有作为activity
的状态记录
这个处理将丢失来自用户对控件的操作,导致UI的状态丢失. Android
为了避免状态丢失抛出了IllegalStateException
异常
什么时候抛出这个异常
如果之前遇到这个问题的话,可能注意到不同的版本抛出的异常有所不同. 越老的设备抛出这个异常的越频繁,使用support
包比使用官方的包出现更多的异常
引起这个问题的原因是在3.1时改变了activity
的生命周期,在3.1之前没有考虑到activity
在被暂停之后被杀,意味着onPause()
之前调用了onSaveInstanceState()
方法. 在3.1时,只考虑了activity
在被停止的时候被杀,意味着onSaveInstanceState()
在onStop()
方法之前被调用,而不是在onPause()
之前调用,这个不同点在下表中给出总结:
这个小的改变源于activity
的生命周期,support
包需要根据版本改变行为,在3.1及以上版本,这个异常将在onSaveInstanceState()
方法被调用之后,调用commit()
方法抛出的,告诉开发者activity
的状态已丢失. 在3.1之前抛出这个异常是限制版本,onSaveInstanceState()
方法的调用早于activity
的生命周期,结果导致当前的状态丢失. android
团队做了一个兼容,老设备在onPause()
和onStop()
之间出现状态丢失,下表给出support
包跨版本的情况:
如何避免这个异常
明白什么原因导致状态丢失后解决这个问题就变得更加容易,希望明白一点support
包的工作原理,为什么要在应用中避免状态丢失是至关重要的,这里给出一些建议保证在应用使用FragmentTransactions
正常返回:
- 在
activity
的生命周期内谨慎的使用commit()
方法,大多数应用只有在第一次onCreate()
时或者响应用户操作时调用commit
,不会抛出这个异常,但是在其他生命周期中使用时可能会抛出这个异常,如onActivityResult()
,onStart()
,onResume()
,不应该在FragmentActivity#onResume()
中commit
,在activity
的状态恢复之前已经调用了这个方法(官网解读),如果应用中不在onCreate()
中commit
,可以在FragmentActivity#onResumeFragments()
或者Activity#onPostResume()
中commit
,这两个方法都是在activity
的状态恢复之后调用,避免了activity
的状态丢失 - 避免在接口回掉中执行操作,接口回掉例如
AsyncTask#onPostExecute()
,LoaderManager.LoaderCallbacks#onLoadFinished()
,在这些方法中执行操作会导致状态丢失,例如下面这个事件:- 在
activity
中使用AsyncTask
- 点击
home
键,导致activity
的onSaveInstanceState()
和onStop()
被调用 -
AsyncTask#onPostExecute()
被调用时,当前的activity
已停止 - 在
onPostExecute()
中commit
,抛出异常
- 在
正常情况下,避免这个异常的最好方式就是避免在回掉中commit
,如果应用中一定要在回掉中用的话,不可避免的会调用onSaveInstanceState()
,可以使用commitAllowingStateLoss()
来解决这个问题
- 使用
commitAllowingStateLoss()
方法作为最后的招数,commit()
和commitAllowingStateLoss()
不同点在于在状态丢失时后者不会抛出异常,通常情况下不会使用这个方法,使用这个方法是就以为着状态可能会丢失,最好的解决办法在activity
状态保存之前在应用中commit
,除非状态丢失不可避免,否则就不要用commitAllowingStateLoss()
希望这几条建议可以帮到你!
网友评论