2.2 Activity生命周期

每一种技术都有其生命周期,例如Java EE技术中的Servlet生命周期就分为4步,即实例化、初始化、服务和销毁。当然Android系统中的Activity也不例外,也有其自己的生命周期过程。本节将讲解Activity的生命周期,并通过实例演示的方法带领读者仔细观察在Activity的生命周期中到底发生了什么。

2.2.1 Activity生命周期概述

Activity是由Activity栈进行管理的。当来到一个新的Activity后,此Activity将被加入Activity栈顶,之前的Activity位于此Activity底部。Activity一般有4种状态:

• 当Activity位于栈顶时,正好处于屏幕最前方,处于运行状态。

• 当Activity失去焦点但仍然对用户可见(如栈顶的Activity是透明的或者栈顶Activity并不是铺满整个手机屏幕)时,处于暂停状态。

• 当Activity完全被其他Activity遮挡时,Activity对用户不可见,处于停止状态。

• 当Activity由于人为或系统原因(如低内存等)被销毁时,处于销毁状态。

在每个不同的状态阶段,Android系统都对Activity内相应的方法进行了回调。在开发过程中写的Activity一般都继承Activity类并重写相应的回调方法。Google官方提供的Android中典型的生命周期流程图如图2-2所示。读者可以通过观察这个图获得一个大致的印象。此图对于理解Activity生命周期很重要,读者可以多做观察,熟记于心。

通过这个Activity流程图,我们可以看出Activity生命周期中的7个事件。

• onCreate():当Activity第一次被创建时调用,可以在这个方法中绑定数据或创建其他视图控件,其中应该注意的问题是,覆写onCreate()方法时尽量将当前的Activity状态保存进系统,以备以后再使用这个Activity时保存以前界面的状态。

• onStart():当Activity变为用户可见之前调用。

• onResume():当Activity可以与用户交互之前调用,也就是Activity对象到达Activity栈的顶部即将成为前台进程时被调用。

• onPause():当系统调用其他Activity对象时调用,可以在这个方法中将当前Activity对象没有保存的数据保存到持久化对象中,也可以在这个方法中结束比较耗费CPU时间的操作,比如动画之类的。用这个方法写的代码要尽量效率高一些,如果这个方法没有执行完,新的Activity对象将不会显示出来,因为会影响客户的体验性。也就是说,新的Activity对象必须等待onPause()方法执行完毕后再显示出来。大多数情况下,在onPause()方法中要关闭onResume()中打开的资源。

图2-2 Activity生命周期流程图

• onStop():当Activity不可视时调用。

• onDestroy():当销毁Activity对象时调用。

• onRestart():当处于onStop()状态的Activity又变为可视时调用。

除了上述的7个生命周期时间之外,还有3个关键的周期循环:

• Activity的完整生命周期,自第一次调用onCreate(Bundle)开始,直至调用onDestroy()为止。Activity在onCreate()中设置所有“全局”状态以完成初始化,而在onDestroy()中释放所有系统资源。

• Activity的可视生命周期,自onStart()调用开始,直到相应的onStop()调用为止。在此期间,用户可以在屏幕上看到此Activity,尽管它也许并没有位于前台或者正在与用户做交互。

• Activity的前台生命周期,自onResume()调用起,直至相应的onPause()调用为止。在此期间,Activity位于前台最上面并与用户进行交互。

2.2.2 Activity生命周期实例

接下来我们就结合实例详解Activity生命周期的每一个过程,让读者能够更加深刻地体会Activity的生命周期。创建一个Activity并命名为LifeActivity,如下所示:

在上述代码中,除了常用的7种事件方法外,还使用了onWindowFocusChanged、onSaveInstanceState、onRestoreInstanceState三种方法。下面分别对这3种方法进行介绍。

• onWindowFocusChanged方法:在Activity窗口获得或失去焦点时被调用,例如创建时首次呈现在用户面前;当前Activity被其他Activity覆盖;当前Activity转到其他Activity或按Home键回到主屏,自身退居后台;用户退出当前Activity。当Activity被创建时onWindowFocusChanged方法在onResume之后被调用,当Activity被覆盖或者退居后台或者当前Activity退出时在onPause之后被调用。

• onSaveInstanceState:在Activity被覆盖或退居后台之后,系统资源不足将其杀死,此方法会被调用;在用户改变屏幕方向时,此方法会被调用;在当前Activity跳转到其他Activity或者按Home键回到主屏,自身退居后台时,此方法会被调用。第一种情况不知道会在什么时候发生,系统根据资源紧张程度去调度;第二种情况出现在屏幕翻转方向时,系统先销毁当前的Activity,然后重建一个新的,调用此方法时,我们可以保存一些临时数据;第三种情况下调用此方法是为了保存当前窗口各个View组件的状态。onSaveInstanceState调用在onPause之前。

• onRestoreInstanceState:在Activity被覆盖或退居后台之后,系统资源不足将其杀死,然后用户又回到Activity时,此方法会被调用;在用户改变屏幕方向时,重建的过程中,此方法会被调用。我们可以重写此方法,以便恢复一些临时数据。onRestoreInstanceState调用在onStart之后。

这3种方法在Activity的生命周期中也发挥着重要的作用,在测试时读者可以更加清晰地看出来。

此外,还需要向读者说明上述代码中的android.util.Log类的使用。Log类是Android中用来打印日志的类,常用的方法有5个,即Log.v()、Log.d()、Log.i()、Log.w()及Log.e(),根据首字母对应VERBOSE、DEBUG、INFO、WARN、ERROR。在Android Studio的控制台中打开Android monitor界面,选择具体的Log level,输入关键字即可查看日志。本例中使用Log.i()来记录Activity的执行过程,选择level为info级别,关键字为LifeActivity,如图2-3所示。

图2-3 查看关键字为LifeActivity的Log

下面我们通过运行上面的代码来直观地观察Activity的生命周期。

(1)运行Activity,Log如下:

安装运行Activity,不做其他任何操作,系统会在调用了onCreate和onStart之后调用onResume,这样Activity就进入了运行状态。在onResume之后,系统还调用了onWindowFocusChanged方法。这个方法在某种场合下非常有用,例如程序启动时想要获取特定视图组件的尺寸大小。在onCreate方法执行时,因为窗口Window对象还没创建完成,无法取得视图组件的尺寸大小,这时就需要在onWindowFocusChanged里获取。

(2)跳转到其他Activity或按Home键回到主屏,Log如下:

退居后台时,Activity的窗口焦点发生变化,onWindowFocusChanged首先执行,然后才调用onPause,之后调用onSaveInstanceState方法,最后调用onStop方法。按Home键回到主屏测试,跳转到其他Activity的结果是一样的。等学习完多个Activity之间跳转之后,读者可以自行测试。

(3)从后台进入前台,Log如下:

当从后台回到前台时,系统先调用onRestart方法,然后调用onStart方法,接着调用onResume方法,Activity又进入运行状态,Activity获得焦点,调用onWindowFocusChanged方法。

(4)退出程序,Log如下:

退出程序调用了onDestroy方法。

(5)横屏,Log如下:

通过日志可以发现,一次横屏的过程其实经历了一次调用onDestroy方法销毁Activity以及一次调用onStart方法打开Activity的过程,只是在这个过程中多了一次调用onSaveInstanceState方法保存临时数据,以及调用onRestoreInstanceState方法恢复数据的过程。这个发现非常有用,例如在一个视频App中,当用户正好观看到了一半视频时,切换了屏幕,如果不在onSaveInstanceState方法中保存用户观看的数据信息并在onRestoreInstanceState方法中恢复就会造成视频无法准确定位到用户退出时看到的位置这一尴尬状况。

当然,有时为了省去这些麻烦,开发者也可以指定Activity的屏幕方向,这样就不会存在屏幕切换的生命周期问题了。我们可以为Activity指定一个特定的方向,指定之后即使转动屏幕方向,显示方向也不会跟着改变。在AndroidManifest.xml中对指定的Activity设置android:screenOrientation="portrait",其中portrait为竖屏,landscape为横屏,或者在onCreate使用setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)方法指定,指定ActivityInfo.SCREEN_ORIENTATION_PORTRAIT为竖屏、ActivityInfo.SCREEN_ ORIENTATION_LANDSCAPE为横屏。