解决bug中的总结:Fragment Transactions 和Activity状态丢失
Fragment transactions用于在一个Activity上添加、移除或者替换fragment。大多数时候,fragment transaction会在activity的onCreate()方法中执行,也可能在与用户交互中响应。
- java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
- at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
- at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager:1338)
- at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
- at android.support.v4.app.BackStackRecord.commit(BackStackRecord:574)
- at android.support.v4.app.DialogFragment.show(DialogFragment:127)
原因:不管何时,如果一个FragmentActivity放在后台,对应FragmentMangerImpl中mStateSaved的flag就会设置为true。这个flag是用来检查是否有state loss。
当试图执行一个transaction时,如果这个flag为true,那么就首先会抛出IllegalStateException异常。
那为什么会抛出这个异常呢?这个问题源于这样的事实,Bundle对象代表一个Activity在调用onSaveInstanceState()
方法的一个瞬间快照。
这个transaction将不会被记住,因为它没有在第一时间记录为这个Activity的状态的一部分。Android不惜一切代价避免状态的丢失。
这意味着,当你在onSaveInstanceState()
方法调用后会调用FragmentTransaction的commit方法。因此,在有些时候,都将简单的抛出一个IllegalStateException异常。
Honeycomb之前的版本 更新版本 | ||
Activities会在onPause()调用前被结束? | NO | NO |
Activities会在onStop()调用前被结束? | YES | NO |
onSaveInstanceState(Bundle)会在哪些方法调用前被执行? | onPause() | onStop() |
作为Activity生命周期已做的细微改变的结果,Fragment的Support Library有时候需要根据平台的版本来改变它的行为。
Honeycomb之前的版本 更新版本 | ||
commit()在onPause()前被调用 | OK | OK |
commit()在onPause()和onStop()执行中间被调用 | STATE LOSS | OK |
commit()在onStop()之后被调用 | EXCEPTION | EXCEPTION |
建议一
不要在让transactions在其他的Activity生命周期函数提交,如onActivityResult()
、onStart()
和onResume()
,事情将会变得微妙。
例如,你不应该在FragmentActivity的onResume()
方法中提交transactions。因为有些时候这个函数可以在Activity的状态恢复前被调用。
如果你的应用要求在除onCreate()
函数之外的其他Activity生命周期函数中提交transaction,你可以在FragmentActivity的onResumeFragments()
函数或者Activity的onPostResume()
函数中提交。
这两个函数确保在Activity恢复到原始状态之后才会被调用,从而避免了状态丢失的可能性。
nResume和onResumeFragments的区别是什么呢?下面是官方文档 对FragmentActivity.onResume的解释:
将onResume() 分发给fragment。注意,为了更好的和旧版本兼容,这个方法调用的时候,依附于这个activity的fragment并没有到resumed状态。意味着在某些情况下,前面的状态可能被保存了,此时不允许fragment transaction再修改状态。
从根本上说,你不能确保activity中的fragment在调用Activity的OnResume函数后是否是onresumed状态,
因此你应该避免在执行fragment transactions直到调用了onResumeFragments函数。
建议二
避免在异步回调函数中提交transactions。包括常用的方法,比如AsyncTask的onPostExecute方法和LoaderManager.LoaderCallbacks的onLoadFinished方法。
在这些方法中执行transactions的问题是,当他们被调用的时候,他们完全没有Activity生命周期的当前状态。例如,考虑下面的事件序列:
- 一个Activity执行一个AsyncTask。
- 用户按下“Home”键,导致Activity的
onSaveInstanceState()
和onStop()
方法被调用。 - AsyncTask完成并且onPostExecute方法被调用,而它没有意识到Activity已经结束了。
- 在onPostExecute函数中提交的FragmentTransaction,导致抛出一个异常。
一般来说,避免这种类型异常的最好办法就是不要在异步回调函数中提交transactions。
如果你的应用需要在这些回调函数中执行transaction,而没有简单的方法可以确保这个回调函数不好在onSaveInstanceState()
之后调用。
那么,可能需要使用commitAllowingStateLoss方法,并且处理可能发生的状态丢失。
建议三
作为最后的办法,使用
commitAllowingStateLoss()
函数。commit()
函数和commitAllowingStateLoss()
函数的唯一区别就是当发生状态丢失的时候,后者不会抛出一个异常。当然,更好的解决方案是commit函数确保在Activity的状态保存之前调用,这样会有一个好的用户体验。除非状态丢失的可能无可避免,否则就不应该使用
commitAllowingStateLoss()
函数。