关于onSaveInstanceState 的一些认识

onSaveInstaceState 是Android 系统里的一个重要函数,官方)关于它的基本描述其实已经很明白,这个函数是在系统将要kill 掉你的Activity 之前调用的,从而给你机会保存一些重要数据。并且还好心的指出这个函数并不对Activity 的生存周期产生影响,它仅仅是在上述的“特殊情况”下才会被调用,正常的退出操作并不会 触发它。简单翻译:

用于取出上个实例(就是被kill 掉之前)的状态,从而可以在onCreate 或者onRestoreInstanceState 里恢复。这个函数将会在Activity 被kill 掉之前调用。例如:如果Activity B 从Activity A 被启动,并且在某个时间点A因为系统资源不足被kill 掉了,那么A将会通过这个函数获得机会保存它界面上的状态,这样当用户又切换回A时,保存的状态可以通过onCreate 或者onRestoreInstance 恢复。

不要和Activity 的生存周期函数如onPause 等弄混了,onPause 是只要Activity 被放到后台就一定会被调用的,同样onStop 是在Activity 被销毁之前调用。什么时候onPause 和onStop 会被调用但是这个函数不会呢?那上个例子来说:当用户从B 切换回A 后,B 的onSaveInstanceState 函数是没有必要被调用的,因为B 这个实例已经不可能再被用到了。另外即使当A 处在B 的后面(后台),但是系统没有发现资源紧张的状态时,A 的onSaveInstanceState 函数是不会被调用的,相反,它的onPause 是一定会被调用的( 因为到后台去了)。

在这个函数到默认实现里,它存储了当前Activity 上View 的信息,如果你想要保存自己的一些状态信息,请务必调用默认实现,确保View 的信息被正确的保存。

当初我看完这个说明(印象中早些时候没写的这么详细)后的感想就是这个玩意儿应该没多大用处吧,无非就是快要完蛋时候给个机会保存数据嘛,我完全可以在onStop) 或者onDestroy) 里做啊,反正都是要被调用的。而且保存数据还要传到Bundle 里, 就要求自定义的数据去实现Parcelalbe 接口,太麻烦了。
试着在代码里加log 看了看被调用的情况,更有些摸不着头脑的感觉了:有时在log 里出现了,有时却没有。但有一点确实如官方说明指出的:如果这个函数被调用了,那一定是在onStop之前。

但是我忽视了很重要的一点,就是“快要完蛋”和“正常退出”时保存的数据的用途。“快要完蛋”时系统调用onSaveInstanceState, 之后也确实走了onStop 和onDestroy , 但不同的是,“快要完蛋”之后,系统有可能会再给你机会让你“重生”,也就是recreate 的过程。此时保存的数据会被直接用于Activity 的“重生”——系统帮你做这些事。 而“正常退出”则直接走onStop 和onDestroy ,这时保存的数据是用于下次启动的——你自己做这些事。

要理解这个函数的重要之处,还需要明白onCreate)。它是恢复数据的关键(当然还有一个onRestoreInstanceState),但是这个函数是在onStart 之后被调用,在这里恢复数据有点晚的感觉…)。这个函数带一个参数savedInstanceState,它就是onSaveInstanceState 的结果。流程是这样的:某个时刻系统触发Activity的recreate过程,首当其冲的便是调用onSaveInstanceState,接着便是onStop -> onDestroy 然后将onSaveInstanceState 里保存的状态参数(就是Bundle)传给onCreate,然后就可以在onCreate 里取出之前保存的状态参数进行恢复了。

这里的某个时刻有多种可能性,比如当前Activity 在后台且内存资源紧张,或者系统的配置发生了变化(字体、屏幕方向等)。系统配置变化时也伴随着onConfigurationChanged) 的调用。

说点题外话,虽然可以在AndroidManifest.xml里设置
android:configChange来“避免”Activity被recreate,但是Activity却需要自己调整相应的变化。比如字号改变后,虽然
android:configChange=fontScale可以使Activity不被销毁,但Activity画面上的字号却不会有变化。(不知道Android这么设计
算不算是偷懒…)

另外说到后台,并不是App到了后台就会被调用onSaveInstanceState。其实Activity被切换到后台的过程是很复杂的,系统会
试着衡量被切换到后台的Activity的资源占用等等一些列的信息,从而决定是不是调用这个函数。

一个实际情况就是更改系统字号。首先启动App,然后通过多任务切换(从log 里会发现这个时候onSaveInstanceState 已经被调用了),到设置里改变字号,然后再回到App,此时onStop -> onDestroy -> onCreate 会被依次调用,然后onCreate 的参数里就是之前保存的状态参数了。

所以理论联系实际,如果App没有对界面状态的要求,每次呈现给用户的都是同一个界面,那么完全可以忽视这个过程。但是如果想在发生“意外情况”时也保留当前状态,那么重写这个函数,把想要保留的状态存到Bundle 里就成了必要的选择了。

说到要保存的状态,自然会想到当前View 的状态,比如一个ListView 的当前滑动到的位置等,但是这却是不必要的。因为Android 系统会为你做这些事情,实际上View里面也有个onSaveInstanceState) 函数,作用也是一模一样,只是生存周期不再是Activity 的那套,而是View 自己的(关于View 的生存周期有点麻烦,连Google 自己的人都说不明白;)。系统提供的那些控件(ListView、ScrollView、EditText 等)都会记录自己的状态以待下次被创建时使用。当然自定义的控件里添加的属性还是需要自己想办法来保存的,或者在View的onStateInstanceState 里搞定,又或者在Activity(Fragment)里搞定,具体情况具体对待了。

最后是这个函数被调用并不意味着Activity 就一定会被recreate,比如通过多任务键切换到后台,此时是一定被调用的,但是即使到了后台,如果没有特殊情况发生,用户又切换回来的话,不会伴随onStop -> onDestroy -> onCreate的过程(这也是之前我瞎测时看到的“时有时无”的情况),否则Android 就真是 太傻了。