收集Android实际开发中的bug总结与解决方法(第二节)

 解决bug中的总结:Fragment Transactions 和Activity状态丢失



 
 Fragment transactions用于在一个Activity上添加、移除或者替换fragment。大多数时候,fragment transaction会在activity的onCreate()方法中执行,也可能在与用户交互中响应。
  然而,BUG是当恢复一个activity时,fragment transaction被执行了,应用就可能发生下面的下崩溃:

  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:1338)  
  4.     at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)  
  5.     at android.support.v4.app.BackStackRecord.commit(BackStackRecord:574)  
  6.     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生命周期的当前状态。例如,考虑下面的事件序列:

  1. 一个Activity执行一个AsyncTask。
  2. 用户按下“Home”键,导致Activity的onSaveInstanceState()onStop()方法被调用。
  3. AsyncTask完成并且onPostExecute方法被调用,而它没有意识到Activity已经结束了。
  4. 在onPostExecute函数中提交的FragmentTransaction,导致抛出一个异常。

一般来说,避免这种类型异常的最好办法就是不要在异步回调函数中提交transactions。

如果你的应用需要在这些回调函数中执行transaction,而没有简单的方法可以确保这个回调函数不好在onSaveInstanceState()之后调用。

那么,可能需要使用commitAllowingStateLoss方法,并且处理可能发生的状态丢失。

建议三

作为最后的办法,使用commitAllowingStateLoss()函数。commit()函数和commitAllowingStateLoss()函数的唯一区别就是当发生状态丢失的时候,后者不会抛出一个异常。

当然,更好的解决方案是commit函数确保在Activity的状态保存之前调用,这样会有一个好的用户体验。除非状态丢失的可能无可避免,否则就不应该使用commitAllowingStateLoss()函数。





  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值