[关闭]
@linux1s1s 2015-10-26T02:23:45.000000Z 字数 3612 阅读 2691

Fragment Commit 异常处理

AndroidException


自从Honeycomb发布后,像下面这个栈信息经常会在StackOverFlow中出现

  1. java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
  2. at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
  3. at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1338)
  4. at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
  5. at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)

这篇博客会解释问题出现的原因,并会给出解决这个问题的思路

抛出异常的原因

抛出异常的原因是
在Activity的状态已经保存后commit一个FragmentTransaction,导致了一个现象叫做Activity state loss。

在我们深入细节之前,让我们先看看在onSaveInstanceState()调用后发生了什么。在我的前一篇 Binders & Death Recipients有讨论到,Android应用程序在Android系统中只有很小的控制权。Android系统为了释放内存可以在任意时刻停止进程,然后处于后台的Activity就会被毫无警告地杀掉。为了保证因此引起的不稳定行为避免用户知道,Android框架为每一个Activity通过调用onSaveInstanceState()来保存自己状态的机会,它会在Activity可能被销毁之前调用。当后面恢复状态的时候,用户不会感觉到Activity已经被系统杀掉了,而会感觉前台和后台的Activity无缝切换。
当Android框架调用onSaveInstanceState(),并传递一个Bundle对象,以便Activity恢复状态。Activity可以将它的Dialog、fragment以及view的状态保存在Bundle中。当这个方法返回的时候,系统通过Binder打包Bundle对象然后传给系统服务进程。系统服务进程负责保证Bundle对象安全地保存下来。当系统决定重新创建Activity后,会取出这个Bundle对象,以便复原Activity状态。
所以为什么这个异常会抛出?原因是那些Bundle对象代表Activity在onSaveInstanceState()被调用时的一个快照。这就意味着当你在onSaveInstanceState()之后调用FragmentTransaction#commit()的时候,transation不会被记录,所以之前Activity的状态没有被完整保存。从用户的角度来说,这个transaction就像丢失了,导致UI状态意外的丢失。为了保证用户体验,Android会不计一切代价避免状态丢失,也就是当它发生的时候会简单地抛出一个IllegalStateException。

这个异常什么时候会抛出?

如果你之前已经碰到过这个异常,你可能会注意到异常抛出的时机因不同的Android版本而不同。比如,老版本的设备上,这个异常很少抛出,而当你的程序中使用support library而不是官方框架中的类时却容易触发这个异常。这些轻微的差别让很多人都以为support library有bug,不值得信任。然而,这些假设存在误解。

这些差别是因为在Honeycomb版本中,Activity生命周期有了重要的变化。Honeycomb之前版本,activity在onPause()之前不会被杀掉,这意味着onSaveInstanceState()会在onPause()之前被调用。而从HoneyComb开始,Activity只会在OnStop()后被杀掉,这意味着onSaveInstanceState()会在onStop()之前被调用而不是在onPause()之前。这些变化在下表中总结:

知识点 Honeycomb之前 Honeycomb之后
Activity是否可以在onPause()之前被杀掉? NO NO
Activity是否可以在onStop()之前被杀掉? YES NO
onSaveInstanceState(Bundle) 保证在...之前被调用 onPause() onStop()

怎么避免这个异常?

总之,在这些案例中避免异常抛出的最优方法就是避免在异步回调方法中commit transactions。Google工程师似乎同意这个见解。根据在Android Develop group上的这篇文章,Android开发团队认为通过commit FragmentTransactions来让UI产生重大的变化对用户体验十分不友好。如果你的应用程序需要在这些回调方法中执行transaction,那么没有什么简单方法可以保证这些回调不会再onSaveInstanceState()后调用,你可能必须使用commitAllowStateLoss()并且处理可能发生的状态丢失。(详见两篇StackOverFlow文章,文章1、文章2)

小结

Commit FragmentTransaction ----> activity onSaveInstanceState(Bundle)(Activity状态保存) Right

OnResume() Called ----> activity restored(Activity被恢复) ---> Commit FragmentTransaction Right

用图形象表示如下:
此处输入图片的描述

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注