首页 » Android程序设计:第2版 » Android程序设计:第2版全文在线阅读

《Android程序设计:第2版》对生命周期进行可视化

关灯直达底部

从本书前面的部分及Android开发者文档中,你可能已经了解到了组件生命周期流图,并了解了生命周期如何工作。这些描述的问题在于组件生命周期是动态的,而状态图是静态的。此外,组件和过程生命周期转换是由内存管理驱动的:当消耗完内存,在组件生命周期内会恢复内存。内存分配、垃圾收集及跨进程的内存恢复方式在本质上没有运行代码块那么明确,而且是依赖于配置的。通过检测和运行代码,将会看到应用的生命周期,可以在运行程序中进行实验。

对活动生命周期进行可视化

下面将通过运行检测程序使得Activity组件生命周期更清晰,并使用Eclipse中的LogCat视图,观察Activity生命周期的行为。以下代码是Activity子类列表,实现了生命周期方法,并在每个方法中实现了日志记录。代码中标注的生命周期是指在后文中的按方法解释的生命周期处理。查看该列表,看要记录什么样的信息:


package com.oreilly.demo.pa.ch10.finchlifecycle;import android.app.Activity;import android.os.Bundle;import android.util.Log;import android.view.Menu;import android.view.MenuItem;public class FinchLifecycle extends Activity {    // Make strings for logging    private final String TAG = this.getClass.getSimpleName;    private final String RESTORE = ", can restore state";    // The string "fortytwo" is used as an example of state    private final String state = "fortytwo";    @Override    public void onCreate(Bundle savedState) {①        super.onCreate(savedState);        setContentView(R.layout.main);        String answer = null;        // savedState could be null        if (null != savedState) {            answer = savedState.getString("answer");        }        Log.i(TAG, "onCreate"            + (null == savedState ? "" : (RESTORE + " " + answer)));    }    @Override    protected void onRestart {②        super.onRestart;        // Notification that the activity will be started        Log.i(TAG, "onRestart");    }    @Override    protected void onStart {③        super.onStart;        // Notification that the activity is starting        Log.i(TAG, "onStart");    }    @Override    protected void onResume {④        super.onResume;        // Notification that the activity will interact with the user        Log.i(TAG, "onResume");    }    protected void onPause {⑤        super.onPause;        // Notification that the activity will stop interacting with the user        Log.i(TAG, "onPause" + (isFinishing ? " Finishing" : ""));    }    @Override    protected void onStop {⑥        super.onStop;        // Notification that the activity is no longer visible        Log.i(TAG, "onStop");    }    @Override    protected void onDestroy {⑦        super.onDestroy;        // Notification that the activity will be destroyed        Log.i(TAG,                "onDestroy "                        // Log which, if any, configuration changed                        + Integer.toString(getChangingConfigurations, 16));    }    // ////////////////////////////////////////////////////////////////////////////    // Called during the life cycle, when instance state should be saved/restored    // ////////////////////////////////////////////////////////////////////////////    @Override    protected void onSaveInstanceState(Bundle outState) {⑧        // Save instance-specific state        outState.putString("answer", state);        super.onSaveInstanceState(outState);        Log.i(TAG, "onSaveInstanceState");    }    @Override    public Object onRetainNonConfigurationInstance {⑨        Log.i(TAG, "onRetainNonConfigurationInstance");        return new Integer(getTaskId);    }    @Override    protected void onRestoreInstanceState(Bundle savedState) {⑩        super.onRestoreInstanceState(savedState);        // Restore state; we know savedState is not null        String answer = null != savedState ? savedState.getString("answer") : "";        Object oldTaskObject = getLastNonConfigurationInstance;        if (null != oldTaskObject) {            int oldtask = ((Integer) oldTaskObject).intValue;            int currentTask = getTaskId;            // Task should not change across a configuration change            assert oldtask == currentTask;        }        Log.i(TAG, "onRestoreInstanceState"                + (null == savedState ? "" : RESTORE) + " " + answer);    }    // ////////////////////////////////////////////////////////////////////////////    // These are the minor life cycle methods, you probably won't need these    // ////////////////////////////////////////////////////////////////////////////    @Override    protected void onPostCreate(Bundle savedState) {⑪        super.onPostCreate(savedState);        String answer = null;        // savedState could be null        if (null != savedState) {            answer = savedState.getString("answer");        }        Log.i(TAG, "onPostCreate"                + (null == savedState ? "" : (RESTORE + " " + answer)));    }    @Override    protected void onPostResume {⑫        super.onPostResume;        Log.i(TAG, "onPostResume");    }    @Override    protected void onUserLeaveHint {⑬        super.onUserLeaveHint;        Log.i(TAG, "onUserLeaveHint");    }}  

运行应用的准备工作完成后,首先选中Window→Show View→Other命令,显示LogCat视图,展开Show View对话框的Android文件夹。然后选择LogCat,如图10-1所示。

图10-1:从显示的列表中选中LogCat视图

现在在模拟器或物理设备上运行该应用。因为本章给出的实例是基于Android API 11级别的Fragment API(对应于Android 3.0版本的Honeycomb)和Android Support Package中的Fragment类构建的,故你可以以任何一种代码来运行该实例。

运行后将首先看到Eclipse的LogCat视图中的日志信息。只需要查看前文列表中的代码输出的日志信息,可以过滤掉日志信息。单击日志窗口工具栏中绿色的加号,弹出日志过滤器对话框的定义,如图11-2所示。

在这个例子中,可以基于在Finch Lifecycle类中使用的标签来过滤日志,它刚好是类的名字FinchLifecycle。把该过滤器命名为activity-lifecycle,如图10-2所示。

图10-2:定制过滤器,只显示标签为FinchLifecycle的日志数据

现在,当运行程序时,会看到LogCat视图中有一个标签为activity-lifecycle的选项卡,其中只有在活动生命周期方法中输出的日志,如图10-4所示。如果想查看所有的日志信息,则可以查看Log选项卡,其中显示的是未过滤的日志。

当运行程序时,你会发现如果使用模拟器来运行Android 3.0,会出现如图10-3所示的情况。

我们在这里使用Android 3.0是因为本章涉及了生命周期和Fragment类。

图10-3:在Android 3.0模拟器上运行本章示例代码

注意:如果想在设备或模拟器上运行程序,则可以使用示例的backported版本,它利用了Android Support Package,它支持Fragment和Android API 11级到API 4级别的类和Android 1.6对应。

在LogCat视图的activity-lifecycle选项栏,首先看到的是如图10-4所示的日志消息。

为了生成有意义的日志信息,可以启动应用并来回操作,使用应用切换开关或启动器返回到Finch应用。启动其他的应用,在返回到Finch应用时,会看到进程ID(即PID)发生了改变,但是应用的状态看起来和之前的一致。这是因为该活动的状态已经保存了,而且应用的所有其他组件状态也保存了。图10-5所示的日志信息截图说明了这种转换。

图10-4:显示新的进程及保存的活动状态的日志输出

图10-5:输出日志说明了正在恢复的新进程和活动的状态

注意:如果发现在LogCat视图中没有输出,则切换到DDMS透视图方式(使用Window菜单),并单击设备或正在使用的Devices视图中的模拟器。

启动其他需要内存的应用,会触发Android恢复内存的一些策略。当然,因为Android应用运行在Java之类的虚拟机上,所以第一件事就是垃圾回收,即回收那些闲置的、没有引用的对象实例。Android为垃圾回收增加了另一种策略:用户看不见的活动组件可以保存其状态,然后“销毁”这些组件,实际上只是系统消除这些组件的引用,然后可以对它们执行垃圾回收。Android还提供了另一种内存恢复策略:通知应用中的所有组件保存其状态,然后可以删除整个进程,恢复内存。这就是Android支持的跨越多个进程的“垃圾回收”方式。

内存恢复和生命周期

Android活动的生命周期似乎很短暂。可以“杀死”活动进程或“销毁”Activity对象。最重要的是,甚至无法保证所有的生命周期中的方法能够在该进程被杀死之前会得到调用。

理解Android生命周期的一个良好的基础是把重点放在当销毁Activity实例和杀死进程后会发生什么。

销毁活动

当Android想要丢弃某个Activity类的实例时,它会调用onDestroy方法销毁活动。discard是指Android系统会设置其到Activity实例的引用为null。这意味着除非你的代码保存了指向该活动的引用,否则该活动会逐渐被垃圾回收。destroy这个词有时不易理解——它表示主动删除。

在调用onDestroy方法后,可以确定该Activity类的实例不会再被使用,但是它并不表示应用,或者其所在的进程会停止运行。实际上,可以初始化并调用同一个Activity子类的实例。例如,在配置改变(如改变屏幕方向)时导致之前使用的Activity类被删除,因而资源加载会重新以新的配置启动执行。

终止进程

当Android系统的内存耗光时,它会杀死进程。通常情况下,Android应用运行在不同的进程中,因此垃圾收集不能够回收Android系统中的所有内存。这表示在内存低的情况下,Android会查找不用的组件并终止它们。在极端情况下,Android还会终止正在使用的组件。在简单的应用中,这些进程在调用onPause方法后就成为要被终止的候选进程。也就是说,如果Android系统需要终止某些进程来获取内存,那么在onPause方法之后调用的所有其他的Activity生命周期内的方法都无法确保会被调用。

在这两个例子中,应用可能需要保存一些状态,它临时保存在应用的用户界面中:尚未处理的各种用户输入,一些非数据模型的可视化指示符的状态等。这就是为什么应用的每个组件,尤其是每个活动,需要覆盖一些生命周期方法的原因。

Activity类的生命周期方法

我们知道了通常情况下何时以及为何调用生命周期方法,下面一起来查看在前文给出的代码中的各个方法及它们都执行了什么。

① 创建Activity实例后会调用onCreate方法。这是大多数应用执行大部分初始化工作的原因:读取布局,创建View实例,绑定数据等。注意,如果没有销毁Activity实例,进程也没有被杀死,就不会再调用。只有当创建新的Activity类的实例时才会调用。该方法的参数是Bundle对象,它包含了保存的应用状态。如果不存在保存的状态,则该参数的值会是null。

② 只有当活动停止后,才执行onRestart方法。“停止”是指该活动不在前端和用户交互。在onStart方法之前会调用该方法。

③ 当Activity对象及其视图可见时,会调用onStart方法。

④ 当Activity对象及其视图和用户交互时,会调用onResume方法。

⑤ 当不同的Activity实例可见并且当前的Activity已经停止和用户交互时,会调用onPause方法。

⑥ 当Activity不可见或不再和用户交互时,会调用onStop方法。

⑦ 当Activity实例要被删除时,会调用onDestroy方法——以后不再使用该方法。在调用这个方法之前,该活动已经停止和用户交互,并且在屏幕上不可见。如果由于调用finish方法执行该方法,则调用isFinishing会返回true。

保存和恢复实例状态

Activity子类保存状态是为了恢复内存和组件生命周期。下面介绍如何保存状态及何时保存。

有一个Bundle类,以键-值形式保存序列化数据。这些数据可以是基础类型,也可以是实现了Parcelable接口的任何类型(见P118“Parcelable”一节)。可以在Android Developers网站上找到更多关于Bundle的信息:http://developer.android.com/reference/android/os/Bundle.html。为了保存Activity实例的状态,需要使用Bundle类的put方法

在调用onCreate方法和onRestoreInstanceState方法时,会向该方法传递Bundle对象。Bundle对象保存了该Activity类的前一个实例所保存的数据,使得它能够在不同实例之间共享。也就是说,如果Activity实例包含状态,除了数据模型中所保存的,在该Activity类的多个实例之间可以保存和恢复这些数据。从用户角度来看,似乎是从之前的状态继续执行,而实际上可能是Activity类的全新实例,可能在全新的进程中运行。

你可能已经注意到onPause生命周期方法并未提供Bundle对象来保存状态。因此,什么时候保存状态呢?在Activity类中,有多种方法来保存状态以及通知状态正在恢复。

⑧ 这时应用可以有机会保存实例状态。实例状态应该不会在应用的数据模型中持久存在,例如indicator状态或者Activity对象的其他状态。在父类中包含该方法的实现:它调用该Activity实例的每个View对象的onSaveInstanceState方法,它能够保存这些View对象的状态,而且是你需要以这种方式保存的唯一状态。子类需要保存的数据是通过Bundle类的put方法保存的。

⑩ 当要恢复实例状态时,要调用onRestoreInstanceState方法。onRestoreInstanceState方法是在onStart方法和onPostCreate方法之间调用的,它是在P287“Activity类的生命周期方法”后文中描述的小生命周期方法。

配置变化和活动生命周期

在前面,我们探讨了通过启动足够的应用来触发Android系统杀死activity和应用的每个其他组件。如图10-5所示的截图日志,显示了进程ID的变化,并创建了定义该应用如何和用户交互的Activity子类的新实例。该新的实例会重新加载该activity的所有资源,而且如果需要加载某些应用数据,则会重新加载这些资源。其带来的直接影响是用户看不出变化并继续执行操作:新的实例看起来和老的实例相似,因为它们的状态相同。

还有另一种方式可以强制Android使用新的Activity实例:改变系统的配置。应用最常遇到的配置变化是屏幕方向的变化。有很多可以影响配置更新的因素:键盘是否可访问、语言环境、字体大小变化等。所有配置变化的共有特征是,它们需要重新加载资源,因为通常需要重新计算布局。

确保活动中的每个资源都根据新的配置加载的最简单的方式是删除活动的当前实例,并创建新的实例,从而重新加载所有资源。要使在模拟器上运行的应用重新加载资源,按下数值键9。它会改变模拟器的屏幕方向。在日志中,会看到如图10-6所示的屏幕截图。从日志中,会发现由于删除Activity实例,配置发生变化,从而会调用onDestroy方法,而不是当系统由于内存低杀死进程时调用。你还会注意到对于多个Activity对象的新实例,进程ID保持不变——系统不需要从使用的应用中恢复内存。

这种做法看起来很浪费:创建Activity的新实例?有什么用呢?为什么不能保留原来的实例?这样不会太慢吗?但是,在大多数情况下,活动在启动时加载的资源构成了该Activity实例的大部分状态。在很多情况下,活动中发生的大部分计算是在读取XML文件并计算布局时发生的,而且,在大多数情况下,配置变化(如屏幕方向变化或语言环境变化)时几乎所有的资源都需要重新计算其布局。因此,把配置修改成使活动重启动是不可避免的。

图10-6:当调用onDestroy方法时,PID保持不变

记住,当Android“销毁”一个activity时,唯一发生的是消除了到该活动的引用,该引用最终会被垃圾回收。每次当用户从一个活动移动到另一个活动时,会执行创建该活动的所有计算。当配置发生变化时,将配置变化转换为活动的重启动作是不可避免的,所需的计算量也与重启基本相同。

Activity类的小生命周期方法

有另外几个方法,在生命周期过程中也会被调用,但它们不是在Android文档中描述的活动的主生命周期方法。

 onPostCreate方法是在onRestoreInstanceState方法后调用的。如果应用需要恢复两个阶段的状态,则可以使用onPostCreate方法。它是通过传递包含该实例状态的Bundle对象实现的。

 onPostResume方法是在onResume方法后调用的,这时Activity实例应该是可见的而且在和用户交互。

 当由于用户操作导致活动不可见并停止和用户交互时,会调用onUserLeaveHint方法——例如,按下后退键或home键。这是清除警告和对话的很便捷的方式。

在图10-6所示的进程列表中,可以看到我们覆盖了这些方法,从而可以通过日志查看什么时候调用了这些方法。例如当你需要额外的阶段来保存实例状态时,会需要这些方法。

但是,如果你真的需要在配置变化之间保存一些数据,而且这些状态不能在实例之间的Bundle对象中保存,而且要保存的不是数据模型,那么可以使用onRetainNonCon-figurationInstance方法来“隐藏”到一个对象的引用。新的Activity实例可以通过getLastNonConfigurationInstance方法请求该引用。

⑨ 在onStop方法之后会调用onRetainNonConfigurationInstance方法,这表示不能够保证该方法会被调用,而且即使被调用,也不能保证其返回的引用会被保存并提供给后续的Activity实例。可以在onCreate方法中调用getLastNonConfiguration-Instance方法,或者在后续恢复活动状态时可以调用该方法。

为了说明这些方法的使用方法,当调用onRetainNonConfigurationInstance方法时,我们返回包含该活动的任务ID的对象,而当调用onRestoreInstanceState(Bundle)方法时,我们会检查该任务ID。这种方式可以保证即使组件实例或整个进程对于用户而言都已经变化,但从系统角度看它们还是同一个任务。

这些方法最常见的应用场景是保存Web查询结果:可以重新执行查询,但是到Web服务器的延迟可能是数秒的时间。因此,如果系统不能保存,则可以重新创建数据检索新Activity对象,但是如果缓存这些数据则有很大优势。但是,在本书第13章中,我们将向你说明在RESTful应用中如何使用本地数据库进行缓存以减少这种类型的优化需要。