Android - java.lang.IllegalStateException 剖析

以下堆栈信息日志是常见的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.
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 今日是进入实训中心上的第一堂网络营销课程,这堂课让我初步的了解到了网络营销这么专业,我相信大多数人都认为网络营销...
    魏湘粤阅读 1,625评论 2 3
  • 2017.8.10
    cyxdac阅读 1,332评论 0 0
  • 突然在朋友圈看到这张图片,感觉无比温暖,我是一个爱狗人士,每当朋友家有“新朋友”到来,我常会去会会这些机灵的小生灵...
    方政博阅读 4,166评论 0 1
  • 内在的纪律 今天是二月的最后一天,也是应童老师带领的第一期“心灵写作实战营”打卡活动的最后一天。 写下这一篇小文,...
    绵绵雨丝阅读 4,010评论 2 0