java.lang.IllegalStateException,
最近在bugly看到两个关于java.lang.IllegalStateException的异常,一个是java.lang.IllegalStateException: Activity has been destroyed。如果不懂的请参考java.lang.IllegalStateException: Activity has b... - 简书,还有一个就是本文讲的一个异常情况。不过出现本文这种异常还有一种情况。按照我以往的风格直接贴上bug详情吧。


一种是调用DialogFragment的dismiss()方法产生的异常,一种是点击back键产生的异常 。项目中采用DialogFragment来实现加载框的效果,至于为啥要用DialogFragment如果不懂请参考DialogFragment的整理,看bugly上面的详情我们可以知道这个bug是在调用DialogFragment的dismiss方法时产生的或者是点击back键产生的。这两种情况产生的原因是一致的,下面我重点讲解一下本文异常的第一种情况。不过说到这里我们首先要弄清一个知识点,onSaveInstanceState这个方法啥时候回调用呢?
概括的讲,onSaveInstanceState 这个方法会在activity 将要被kill之前被调用以保存每个实例的状态,以保证在将来的某个时刻回来时可以恢复到原来的状态,但和activity 的生命周期方法onStop 和 onPause 不一样,与两者并没有绝对的先后调用顺序,或者说并非所有场景都会调用onSaveInstanceState 方法。那么onSaveInstanceState 方法何时会被调用呢,或者这么问,什么时候activity 会被系统kill 掉呢?有以下几种比较常见的场景:
(1)用户主动按下home 键,系统不能确认activity 是否会被销毁,实际上此刻系统也无法预测将来的场景,比如说内存占用,应用运行情况等,所以系统会调用onSaveInstanceState保存activity状态 ;
(2)activity位于前台,按下电源键,直接锁屏;
(3)横竖屏切换;
(4)activity B启动后位于activity A之前,在某个时刻activity A因为系统回收资源的问题要被kill掉,A通过onSaveInstanceState保存状态。
接下来我们来分析一下为啥调用dismiss()产生bug的原因了,看到这里一定会有人说看源码不就得了啊!没错你说的太对了,那么我们就从dismis()的源码里面分析了。首先dismiss回调用dismissInternal(false)

继续看源码:

有没有觉得很熟悉啊!我们继续看commit()源码,

有人看到这里觉得眼花了,不过你要相信自己的眼睛.没错又看到了熟悉的代码了吧!commitInternal(false)


因为allowStateLoss为false因此回调用checkStateLoss(),至此我们可以很清楚的知道了bugly上面介绍的报错的内容了。

那我们就来查看一下哪个地方会将mStateSaved置为true,经过查找我们发现saveAllState()方法里面

接下来我们就来查找一下saveAllState()方法被调用的地方。我们知道activity生命周期有一个方法是onSaveInstanceState(Bundle outState)

继续看源码



至此我们终于可以知道了报错的原因所在了,我们的activity在某种场景下处于被kill 掉的边缘,系统就调用了onSaveInstanceState 方法,这个方法里面会调用 FragmentManager saveAllState 方法,将DialogFragment 的状态保存,如果此时调用DialogFrament的dismiss()方法就会报这个异常了。因此我们可以调用DialogFragment的dismissAllowingStateLoss()方法来替换掉dismiss(),说到这里也许有人会问dismissAllowingStateLoss最终也会走enqueueAction方法,allowStateLoss为true那么可能会报 "Activity has been destroyed"这个异常了,你这个问题问的非常好,不过如果你看了我上一篇的文章你也许就知道你想要的答案了。

第二种情况是因为activity调用了onSaveInstanceState 方法,这个方法里面会调用 FragmentManager saveAllState 方法,将fragment 的状态保存,在状态保存后用户又主动调了 onBackPressed ,而这个方法的超类super.onBackPressed 方法会判断FragmentManager 是否保存了状态,如果已经保存就会抛出IllegalStateException 的异常 。源码如下:


又看到了熟悉的方法checkStateLoss();然后你就知道了问题的所在了 针对onbackpress 导致的异常,也有几种解决手段
(1)在api11 以上 不调用super 的onSaveInstanceState 方法
protectedvoidonSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
这样做的后果会导致activity 和 fragment 的所有状态,在activity 被系统杀死掉后无法保存,所以如果有保存状态的需要,这个方法是不适用的。
(2)重写 onBackPressed 方法,不调用super.onBackPressed ,直接调用finish 。原因很简单,super.onBackPressed 里面会调用FragmentManager popBackStackImmediate() 方法,如果直接掉finish 就不会触发异常,但这种情况只建议在没有使用 Fragments api时调用。
(3)通过反射手段在onSaveInstanceState 方法里调用 FragmentManagerImpl noteStateNotSaved方法将 mStateSaved 变量置为false ,这样既不会导致activity状态丢失,也能确保退出时不会抛出异常,算是比较优雅的处理途径。
至此:本篇文章结束,谢谢!
