3.4 修改活动及其生命周期

13 修改活动及其生命周期1

当使用Android应用时,旋转设备的情况经常发生。这时会发现当进入主界面前如果旋转设备,延时计数会重新开始,如图3-9所示。这是怎么回事呢?

图3-9 界面旋转

原因是Android看到屏幕方向和屏幕大小发生变化时,会撤销这个活动,然后重新创建SplashActivity活动,seconds值被重新赋为6,onCreate()方法会运行,然后再执行goToMain()方法。

Android会运行并启动一个活动,它会考虑设备的配置。这是指物理设备的配置(如屏幕大小、屏幕方向,以及是否有一个关联的键盘),另外还会考虑用户指定的配置选项(如本地化环境)。Android启动活动时需要知道设备配置是什么,因为这可能会影响应用所需的资源。例如,如果设备屏幕是水平而不是垂直的,可能就需要一个不同的布局;另外如果本地化环境是英语环境,可能需要一组不同的字符串值。设备配置改变时,显示用户界面的所有组件都需要更新,从而与新配置一致。如果旋转了设备,Android会发现屏幕方向和屏幕大小有变化,它会把这种情况归为设备配置的变化,所以会撤销当前活动,然后重新创建这个活动,以便选择适合新配置的资源。

Android创建和撤销活动时,这个活动会从启动状态进入运行状态,然后再被撤销。活动的主状态就是运行或活动状态。如果一个活动在屏幕前台,而且得到了焦点,用户可以与之交互,这个活动就处于运行状态。活动的一生(生命周期,lifecycle)的大部分时间都处于这个状态,如图3-10所示。

图3-10 活动的生命周期

活动从启动状态到撤销状态,会触发几个关键的活动生命周期方法:onCreate()和onDestroy()。它们是活动继承得到的生命周期方法,如果需要,可以重写(override)这些方法。活动启动后会立即调用onCreate()方法。活动的所有设置工作就在这个方法中完成,如调用setContentView()方法。一般都要重写这个方法。如果没有重写,可能就无法告诉Android你的活动要使用哪个布局。onDestroy()方法是活动撤销前最后调用的方法。很多情况下有可能会撤销活动,如活动完成,或者由于设备配置发送变化导致活动重建,也可能是Android决定撤销这个活动以节省空间。

在SplashActivity中,SplashActivity类继承了android.app.Activity类。正是因为这个类,活动才可以访问Android生命周期方法,继承关系如图3-11所示。

图3-11 继承关系

Context(android.content.Context)是一个抽象类,是应用环境全局信息的一个接口。运行访问应用资源、类和应用级操作。ContextWrapper(android.content.ContextWrapper)是Context的一个代理实现。ContextThemeWrapper(android.view.ContextThemeWrapper)允许修改ContextWrapper中的主题。Activity(android.app.Activity)类实现了生命周期方法的默认版本。它还定义了另外一些方法,如findViewById(Int)和setContentView(View)。YourAcitvity(cn.edu. jsit.smartfactory)是用户类,在用户类中活动的大多数行为都由父类方法处理,当需要时可以重写部分方法。

上面这种在旋转设备时出现的问题如何解决?也就是该如何处理配置变化,避开重建活动呢?有两种方法。

第一种方法是可以告诉Android不要重启活动,或者可以保存活动的当前状态,这样活动就能自行重新创建并恢复到原来的状态。

如果选择这种方法,可以在AndroidManifest.xml文件的Activity元素中增加一行,如下所示。

configration_change是配置改变的类型,通过设置告诉Android不要考虑屏幕方向和屏幕大小的变化。

第2行说明要避开这两种配置变化。如果Android遇到这种配置变化,它会发出一个onConfigrationChanged(Configuration)方法调用而不是重新创建活动,如果需要,可以实现这个方法对配置变化做出反应。

第二种方法是保存当前状态,当活动重建时,在onCreate()中恢复当前保存的状态。要实现活动状态的保存,需要用到onSaveInstanceState()方法,这个方法在活动撤销之前会被Android调用,可以利用它来保存某些值。

onSaveInstanceState()方法有一个参数Bundle,利用它的put*("name",value)方法可以把不同类型的数据以键/值(Key-Value)对添加到一个对象中。当SplashActivity活动在配置变化被撤销时,该方法会被Android调用,将当前的seconds保存在savedInstanceState对象中。在SplashActivity的onCreate()中可以再从该对象中得到seconds的值。

onCreate()方法有一个参数Bundle,如果这个活动是第一次创建的,这个参数是null;如果这个活动是重新创建的,之前已经调用过onSaveInstanceState()方法,就会把onSaveInstanceState()使用的Bundle对象传递给活动。

14 修改活动及其生命周期2

如果在Splash界面延时计数时,突然进来了一个电话,这时Splash界面就会变得不可见,SplashActivity活动仍然在运行,等电话打完时,应用已经进入主界面了。如果希望电话进来时,SplashActivity活动能停止运行,等电话结束后,SmartFactory应用再次可见时,再恢复运行,这就需要用到活动的其他一些生命周期方法。onCreate()方法和onDestroy()方法可以处理活动的整个生命周期,另外还有3个关键方法可以用来处理活动对用户可见或不可见的工作,分别是onStart()、onStop()和onRestart(),这些方法也是活动从android.app.Activity类继承的。

活动对用户可见时会调用onStart(),不可见时会调用onStop()。可见或不可见可能是因为活动被在它上面显示的另一个活动隐藏,也有可能是这个活动被撤销。如果活动被撤销而调用onStop(),调用onStop()之前会调用onSaveInstanceState()。如果活动不可见,在它重新变为可见时,会调用onRestart()。活动生命周期如图3-12所示。

活动启动并运行onCreate()方法,初始化代码。此时活动还不可见,因为还没有调用onStart()。onStart()方法运行后,用户将在屏幕上看到这个活动。当活动对用户不可见时,onStop()方法被调用,之后活动不再可见。如果活动再次可见时,onRestart()被调用,活动在不可见和可见之间来回切换会反复经过这个循环。撤销活动时会先调用onStop()方法,然后调用onDestroy()方法,如果设备内存很低,onStop()可能被跳过。

图3-12 活动生命周期

为了在电话结束后,让SplashActivity重新恢复运行,可以在onStop()方法中让延时计数停止运行,等到恢复(再次获得焦点)继续运行时再在onStart()方法中让延时计数继续运行。

第5行定义了一个running的变量,记录当前延时计数是否在运行,初始状态为true。

第6行定义了一个wasRunning的变量,记录调用onStop()方法之前延时计数是否在运行,这样就能知道活动再次可见时是否让它再次运行。

第14行当活动重新创建时,恢复wasRunning变量的状态。

第46行保存wasRunning变量的状态。

第49~54行重写了onStop()方法。

第51行调用父类onStop()方法。如果重写一个Android生命周期方法,必须调用父类的生命周期方法,这里是super.onStop()。因为首先要确保这个活动完成父类生命周期方法中的所有动作,如果跳过这一步,Android会抛出一个异常。

第52行将当前running状态赋给wasRunning。

第53行将当前running状态设置为false。

第56~62行重写了onStart()方法。

第58行调用父类onStart()方法。

第59行重建活动时判断原来运行状态,如果为真,则第60行中当前running的状态设置为true(继续延时计数)。

还有一种情况,当SplashActivity活动运行时,另外一个活动出现在它之上,使得SplashActivity失去了焦点,但是这时SplashActivity活动还是可见的,这时SplashActivity会进入暂停状态,SplashActivity活动仍然存在,而且会维护它的所有状态信息,如图3-13所示。

图3-13 失去焦点活动暂停

如果活动可见,但是没有得到焦点(见图3-13左图),活动会暂停。如果得到焦点,活动会继续运行(见图3-13右图)。有两个生命周期方法可以用来处理活动的暂停和活动再次启动,分别是onPause()和onResume()。如果出现左图情况,SplashActivity就会调用onPause()方法,当SplashActivity再次获得焦点时,会调用onResume()方法。如果希望暂停时SplashActivity活动要做一些处理,可以实现两个方法。活动生命周期如图3-14所示。

图3-14 活动生命周期

当活动启动时,运行onCreate()和onStart()方法,这时活动是可见的,但是还没有得到焦点。onResume()方法运行之后,活动得到了焦点,用户可以与它交互。当活动失去焦点时(活动仍可见),onPause()方法被调用。如果活动再次进入前台获得焦点,onResume()会被调用。活动不可见时,onStop()会被调用,活动再次可见时,会调用onRestart()方法,接下来会调用onStart()和onResume()方法。活动从运行状态到撤销状态时,在活动撤销前会调用onPause()方法,通常还会调用onStop()方法。