解决Can not perform this action after onSaveInstanceState异常总结

来源:互联网 发布:已备案域名怎么购买呢 编辑:程序博客网 时间:2024/06/03 21:03

转发请标明来源:http://blog.csdn.net/rflyee/article/details/74723891

上篇博客(这里这里)从源码层面分析了Can not perform this action after onSaveInstanceState异常产生的原因及流程,

接下来分析下该崩溃的设计原因以及如何避。

该异常的设计原因

        大家都知道系统在内存吃紧时会按规则优先kill掉部分非前台activity,为了保证用户体验,系统在kill掉某个activity之前会先调用onSaveInstanceState,将当前window的一些重要状态以bundle的形式持久化,当用户回到该activity时,再通过onCreate、onRestoreInstanceState等方法恢复之前的状态,给用户一种该界面从来没有被kill的假象,从而提升用户体验。

    那么问题来了,在系统onSaveXXX保存状态之后,一直到onRestoreXXX恢复状态之前,这段时间里程序如果再操作window,比如操作FragmentTransaction#commit()、操作FragmentDialog#show()/dismiss(),这些操作肯定没有保存在Bundle中,也就是下次onRestoreXXX恢复状态时是没有这些操作的,也就是所谓的lose state丢失状态,这样可能给用户一种前后不一致的用户体验。所以安卓团队为了“提升用户体验”,saveState之后再操作window就抛出一个IllegalStateException 异常,提醒(逼迫)开发者注意喽。

异常出现时机

如果细心可能会发现,不同的系统异常抛出的时机是不一样的。(还记得上篇文章源码中的saveAllState()方法中改变mStateSaved变量的判断了吗)
原因是3.x之后由于内存吃紧系统主动kill Activity的时机发生了变化,导致onSaveInstanceState的调用时机发生了变化。
变化如下:
    3.x之前:
 onPause()之后activity可能被系统kill。因此:(optional)onSaveInstanceState() -- onPause()
    3.x之后:
    onStop()之后activity可能被系统kill。因此:(optional)onSaveInstanceState() -- onStop()

注意以上高亮的(optional)可选,即并不是说onPause/onStop之前一定调onSaveXXX,而是根据情况有可能调用,详情见这里这里

因此,如果按照正常的设计,在3.x之前的系统onPause()之后如果再进行FragmentTransaction#commit()等操作则会导致崩溃,你懂的,那程序崩的几率可是杠杠的了。因此为了保证更好的用户交互体验,安卓团队做出了妥协,即3.x之前的系统,如果在onPause() - onStop()之间进行FragmentTransaction#commit()类似操作,即使状态丢失也不抛异常,但是如果onStop()之后再进行此类操作则直接抛异常。

引用Alex Lockwood的描述即:

pre-Honeycomb
post-Honeycomb
commit() before onPause()OK
OK
commit() between onPause() and onStop()STATE LOSS
OK
commit() after onStop()EXCEPTION
EXCEPTION

解决方法

理解了异常的原因,就明白我们只能在saveSate之前、restoreState之后进行commit transaction。总结如下
(1)onStop(准确说是onSaveInstanceState)之后不能再执行此类操作,如FragmentTransaction#commit()。因此,可以考虑自己在onSaveInstanceState中添加一个boolean flag,但记得合适时候(onRestoreXXX、oncreate、onPostresume、onResumeFragment)将其reset
(2)在生命周期方法中(除onCreate())commit transactions ,要在FragmentActivity#onResumeFragments() 或者 Activity#onPostResume()里使用。
    RragmentAvtivity#onResume不可以,可以用FragmentAvtivity#onResumeFragments 因为调用onResume时不能保证已经restore之前的state了(假设之前被kill,并saveState),参见 documentation
(3)尽量不要在异步回调中修改状态(commit transaction)。
    比如,activity中执行一个AsyncTask --- 按手机Home回到桌面 --- AsyncTask执行完毕回调onPostExecute() 
--- onPostExecute中进行修改状态 --- 异常。
    同理view.post(runnable) view.postDelay(runnable)都可能出现此情况。
(4)最后的最后,Fragment使用commitAllowingStateLoss代替commit()。前者允许状态丢失,其他完全一样。也即控制着源码中allowStateLoss
(5)有blog说覆写onSaveInstanceState()但是不调用super.onSaveInstanceState(outState);也能避免崩溃,知道原因后的你肯定灰常不建议这么做了(不然onSaveInstanceState还有啥用)。(个人测试,奇酷手机青春版,Android5.1,此方法无效
@Overrideprotectedvoid onSaveInstanceState(Bundle outState){//No call for super(). Bug on API Level > 11.}

试试这样解决,在BaseActivity中添加:
@Overrideprotected void onStart() {    super.onStart();    // super.onStart();中将mStateSaved置为false    mStateEnable = true;}@Overrideprotected void onResume() {// onPause之后便可能调用onSaveInstanceState,因此onresume中也需要置truemStateEnable = true;super.onResume();}@Overrideprotected void onSaveInstanceState(Bundle outState) {// super.onSaveInstanceState();中将mStateSaved置为truemStateEnable = false;super.onSaveInstanceState(outState);}@Overrideprotected void onStop() { // super.onStop();中将mStateSaved置为true mStateEnable = false;super.onStop();}/**  *  * activity状态是否处于可修改周期内,避免状态丢失的错误  * @return  */public boolean isStateEnable() {return mStateEnable;}
之后调用修改状态的方法如fragmentDialog的show()、dismiss()方法等,则先判断状态是否可用。
if(isStateEnable()) {dialog.show();}
 
阅读全文
1 0
原创粉丝点击