以下堆栈信息日志是常见的illegalStateExecption日志。笔者是当应用出于后台时,网络响应更新fragment时出现以下问题。本文将对该异常抛出的的时间和原因进行解释,并对如何避免异常的发生提出一些意见。
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的state已经被保存之后仍然试图调用commit去提交FragmentTransaction. 在详细解析这句话真正意思之前,让我们首先了解一下究竟onSaveInstance里做了什么。安卓系统随时可能杀死进程来释放内存,后台Activity可能随时被杀死。因此Framework通过调用onSaveInstanceState( ) 来保存activity的状态State. FrameWork通过Bundle对象来保存state, 其中记录下dialog,fragment,view等视图结构和状态。这样即便Activity因为异常情况被系统杀死,还是可以通过保存下了的状态来恢复原来的视图。
了解了onSaveInstance具体做了什么之后,再来讨论为什么onSaveInstance被调用之后不能调用commit.原因就是,onSaveInstance已经用Bundle对象将Activity所有状态视图信息保存下来了,这时候想要把这个事务提交上去是不可能的,安卓开发团队出于保护用户界面不被影响,不惜一切代价防止保存的状态不丢失,因此采用抛出异常的方式来解决。听起来有点抽象,其实可以通过一个例子来理解,全班同学的卷子都已经上交并且批改结束,分数排名都出来了。这时候你再提交卷子肯定是不行的,谁知道你有没有偷抄,如果你偷抄的话岂不是影响了班级的排名。
什么时候会抛出异常
如果你之前已经遇到过这样的异常,可能已经注意到在不同的安卓版本上异常抛出的可能性不一样。例如,老的版本抛出异常的概率更小;或者如果应用程序使用support library时抛出异常的概率更大。这会让人觉得是不是support library 有问题。然而事实上不是如此。
抛出异常的概率不同主要原因是Android3.0(HoneyComb)其之后版本中Activity生命周期的显著差异造成的。在3.0以前,onSaveInstance是在onPause调用之前调用,而3.0以后onSaveInstance是在onStop调用之前调用的,这样的话3.0以前在onSaveInstance之后调用commit的概率更高了,因此异常发生的概率也就更高了。
如何避免异常发生
当理解了异常发生背后的真相之后,如何避免就轻而易举了。
-
在Activity生命周期中调用commit一定要注意。
尽量只在onCreate中调用commit.如果冒险在其他生命周期回调函数中调用commit,例如onActivityResult(),onStart(),onResume,就很有可能出问题。例如在onResume中调用commit,由于此时activity的状态信息还没有恢复,就会抛出异常。如果一定要在其他生命周期中调用commit,可以在onResumeFragments或者在onPostResume中调用。这两个方法可以保证activity的状态信息已经恢复了。 -
避免在异步回调中调用commit.
包括AsyncTask.onPostExecute()和LoaderManager.LoaderCallbacks.onLoadFinished().因为在这些回调中调用commit时,不知道当前activity的生命周期走到哪一步,不知道是否当前Activity的状态信息是否已经被保存。下面通过一个事件序列来说明:
1.一个Activity执行了一个AsyncTask.
2.点击Home键,此时onSaveInstanceState和onStop被调用
3.AsyncTask完成调用onPostExecute,但是并不知道此时activity已经结束
4.在onPostExcute中调用commit,将导致异常抛出
总的来说,避免异常的最好方法是避免在异步回调中调用commit。 - 使用 commitAllowingStateLoss()
commit和commitAllowingStateLoss的不同之处在于后者不会抛出异常,即使状态发生丢失。通常情况下不应该使用这个方法,因为这会导致activity的状态信息丢失。因此最好的方法还是在保证activity的状态信息被保存之前调用commit.
网友评论