Activity的生命周期详细分析(二)

来源:互联网 发布:编程模拟蚂蚁寻找甜品 编辑:程序博客网 时间:2024/06/10 20:26

前言

在上一节我们分析了在正常情况下Activity的生命周期的过程,还详细介绍了回调方法的作用,如不是很清晰,建议先去阅读Activity的生命周期详细分析(一)
这一节,我们分析下在异常情下,Activity的生命周期是怎样的?

Activity异常情况下的生命周期

系统在异常情况下(如配置变更)会导致Activity的Destory, 系统会在用户回到这个Activity时有这个Activity存在过的记录,系统会使用那些保存的记录数据(描述了当Activity被Destory时的状态)来重新创建一个新的Activity实例。那些被系统用来恢复之前状态而保存的数据被叫做 “instance state” ,它是一些存放在Bundle对象中的key-value pairs。(请注意这里的描述,这对理解onSaveInstanceState执行的时刻很重要)
根据一些资料,异常情况下主要有2情况:
1.系统配置变更导致异常;
2.资源内存不足导致异常;
但根据文档的一些解释,我发现可以主动在代码上导致生命周期异常,在实际的开发确实会有,所以这里再添加一个,在文章最后稍微提一下。
3.代码主动控制导致异常;

1.系统配置变更导致异常

有些设备配置可能会在运行时发生变化(例如屏幕方向、键盘可用性及语言)。 发生此类变化时,系统调用 onDestroy(),然后立即调用 onCreate(),重建运行中的 Activity。此行为旨在通过利用您提供的备用资源(例如适用于不同屏幕方向和屏幕尺寸的不同布局)自动重新加载您的应用来帮助它适应新配置。
我们用上一节的代码,在实际操作中验证一下是否这样的。运行之后,开始旋转屏幕,我们发现生命周期的回调如下:
这里写图片描述

根据打印看到,没错,确实是这样的。(如果是在SecondActivity旋转,FirstActivity也是会被重新创建的。)

那么问题来了,有人可能会疑问,旋转之后,要重新创建改Activity,那么我的数据会不会清空?(例如输入EditText 中的文本内容)
对于这种情况,可以先实践,我们改一下代码,将布局改为EditText (代码比较简单,这里就不贴),给出最后的效果图看看:
这里写图片描述
这里写图片描述

开心的发现,原来系统已经为我们保存好数据(注意要在java中声明初始化该控件)。是的,即使您什么都不做,也不实现任何方法,Activity 类的 onSaveInstanceState() 默认实现也会恢复部分 Activity 状态。具体地讲,默认实现会为布局中的每个 View 调用相应的 onSaveInstanceState() 方法,让每个视图都能提供有关自身的应保存信息。
接下来我们分析下系统是怎么保存Activity状态的。(参考官方文档
正常情况下,当 Activity 暂停或停止时,Activity 的状态会得到保留。 确实如此,因为当 Activity 暂停或停止时,Activity 对象仍保留在内存中 ,有关其成员和当前状态的所有信息仍处于活动状态。 因此,用户在 Activity 内所做的任何更改都会得到保留,这样一来,当 Activity 返回前台时,这些更改仍然存在。
但在系统在配置变更而销毁某项 Activity 时,Activity 对象就会被销毁。这时,会有2个过程:
保存Activity状态
首先,系统会先调用 onSaveInstanceState(),然后再使 Activity 变得易于销毁,该方法会传递一个 Bundle。如果你的activity也许存在更多你想要恢复的状态信息(例如记录用户Progress的成员变量),你必须重写这个onSaveInstanceState()函数,通过使用 putString() 和 putInt() 等方法以名称-值对形式保存有关 Activity 状态的信息。例如:

static final String STATE_SCORE = "playerScore";static final String STATE_LEVEL = "playerLevel";...@Overridepublic void onSaveInstanceState(Bundle savedInstanceState) {    // Save the user's current game state    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);    savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);    // Always call the superclass so it can save the view hierarchy state    super.onSaveInstanceState(savedInstanceState);}

注:总是需要调用 onSaveInstanceState() 方法的父类实现,这样默认的父类实现才能保存视图状态的信息。而且该方法只有在被异常情况下才会被调用,正常按BACK键是不会调用的。而且,它会在调用 onStop() 之前,进行调用,和onPause() 没有既定的时序。

恢复Activity状态
然后,系统在重新创建的时候,将之前的那个Bundle对象会(系统)被传递到你的activity的onCreate()方法与 onRestoreInstanceState() 方法中。
因为 onCreate() 方法会在第一次创建新的Activity实例与重新创建之前被Destory的实例时都被调用,你必须在你尝试读取 Bundle 对象前Check它是否为null。如果它为null,系统则是创建一个新的Activity instance,而不是恢复之前被Destory的Activity。

你也可以选择实现 onRestoreInstanceState() ,而不是在onCreate方法里面恢复数据。 onRestoreInstanceState()方法会在 onStart() 方法之后执行. 系统仅仅会在存在需要恢复的状态信息时才会调用 onRestoreInstanceState() ,因此你不需要检查 Bundle 是否为null。例如:

public void onRestoreInstanceState(Bundle savedInstanceState) {    // Always call the superclass so it can restore the view hierarchy    super.onRestoreInstanceState(savedInstanceState);    // Restore state members from saved instance    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);    mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);}

注:需要调用onRestoreInstanceState()方法的父类实现,这样默认的父类实现才能保存视图状态的信息。
大致的过程可以参考官方的流程图,如下:
这里写图片描述

我们添加这些保存数据的回调方法,再重新看看打印。
这里写图片描述

问题1:我就是要和系统作对,就是不想默认保存状态,那怎么办?
可以通过在布局文件中将 android:saveEnabled 属性设置为 “false” 或通过调用 setSaveEnabled() 方法,显式阻止布局内的视图保存其状态。
但一般很少这样做,,但如果您想以不同方式恢复 Activity UI 的状态,就可能需要这样做。

问题2:系统变更就要被重建,有没有什么办法不重新创建呢?
在清单文件中编辑相应的 元素,以包含 android:configChanges 属性以及代表要处理的配置的值。android:configChanges 属性的文档中列出了该属性的可能值(最常用的值包括 “orientation” 和 “keyboardHidden”,分别用于避免因屏幕方向和可用键盘改变而导致重启)。您可以在该属性中声明多个配置值,方法是用“|”字符分隔这些配置值。例如:

<activity android:name=".FirstActivity"          android:configChanges="orientation|keyboardHidden"          android:label="@string/app_name">

但是,虽然activity不会重启,它会调用 onConfigurationChanged() ,那么就可以在这个方法处理特殊的事情。(比如:播放器旋转需要重新设置控件的大小)

2.资源内存不足导致异常

该异常在手机不好模拟,但其保存和恢复Activity的过程和情况1完全相同。情况1我们可以自己设置避免发生,但该情况决定权在于系统了。内存不足时,会根据Activity有优先级的情况来决定销毁,从高到低,有这3种情况:

  1. 前台的Activity:在Resumed的状态,和用户交互中的Activity
  2. 可见但非前台 Activity:在Paused的状态,可见但无法和用户交互,比如开启另一个部分透明或未覆盖整个屏幕Activity,
  3. 后台Activity:在Stopped的状态,已经暂停的,优先级最低,最容易被销毁。

3.代码主动控制导致异常

在官方文档有描述:
系统通常是在执行了onPause()与onStop() 之后再调用onDestroy() ,除非你的程序在onCreate()方法里面就调用了finish()方法,。在某些情况下,例如你的activity只是做了一个临时的逻辑跳转的功能,它只是用来决定跳转到哪一个activity,这样的话,你需要在onCreate里面去调用finish方法,这样系统会直接就调用onDestory方法,其它生命周期的方法则不会被执行。

其实这种情况比较少,但也确实见过会有这种需求。在开发IPTV的过程中,进入界面会有3种功能选择,如:IPTV、VOD、YouTube,而且每种功能多会有不同的主题。那么不管点击哪个功能,先进入另一个Activity判断,但该Activity并不需要显示什么视图,只要判断完就可以销毁了,那么我们就在onCrete()方法判断,并执行finish()。这样的话,Activity的生命周期是有变化。可以看打印:
这里写图片描述
我们也可以尝试在其他回调方法执行finish(),
在onStart()中:
这里写图片描述

在onResume中:
这里写图片描述
其他方法执行,和onResume是一样的。
举出这种情况,主要是说明,Activity的生命周期不一定是完全按照正常流程执行的。而且,在文档中写道:“Notice that no matter what scenario causes the activity to stop, the system always calls onPause() before calling onStop().”(无论什么原因导致activity停止,系统总是会在onStop()之前调用onPause()方法)
但是我们看到如果在onStart()就finish()的话,是不会执行onPause()的。
对于情况3,也是在总结的时候发现,还没有太多的认识,如读者有研究可以分享。

总结

在异常情况下,主要是要明白系统是怎么保存和恢复Activity数据的,然后要恢复其他数据,需要重写保存数据方法。如果要避免情况1,在清单文件中自行处理配置变更即可。

好了,对于Activity的生命周期的分析就到这里了,如有误,还请指正。

主要参考:
https://developer.android.google.cn/develop/index.html
https://developer.android.google.cn/training/index.html
http://hukai.me/blog/archives/

1 0