本节书摘来自异步社区《Android开发秘籍(第2版)》一书中的第2章,第2.2节Android的演化,作者 【美】Ronan Schwarz , Phil Dutson , James Steele , Nelson To,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.2 Activity的生命周期
Android开发秘籍(第2版)
应用程序中的每个Activity都要经历自己的生命周期。Activity于onCreate()函数执行时被创建,该创建过程仅会执行一次。退出Activity时,则会执行onDestroy()函数。在此二者之间,各种各样的事件可以使Activity进入各种不同状态,如图2-2所示。下一个技巧将对这些函数一一举例。
技巧4:使用Activity生命周期函数
本技巧提供了一种在Activity工作时查看其生命周期的简单方法。为清楚起见,对每个被重写的函数都予以显式声明,并在其中加入一条Toast命令,这样在执行某个函数时,在屏幕上能有所反映(关于Toast微件的更多细节将在第3章中给出)。代码清单2-7给出了Activity的内容。在Android设备上运行它,并尝试各种情况。特别注意以下几点。
改变屏幕方向将会销毁并重建Activity。
按下Home键会使Activity暂停,但不会销毁它。
点击应用程序图标可能会启动一个Activity的新实例,甚至在旧的Activity并未销毁的情况下也会如此。
让屏幕休眠将会暂停Activity,而在唤醒时会恢复它(与接电话时的状况类似)。
代码清单2-7 src/com/cookbook/activity_lifecycle/ActivityLifecycle.java
package com.cookbook.activity_lifecycle;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
public class ActivityLifecycle extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Toast.makeText(this, "onCreate", Toast.LENGTH_SHORT).show();
}
@Override
protected void onStart() {
super.onStart();
Toast.makeText(this, "onStart", Toast.LENGTH_SHORT).show();
}
@Override
protected void onResume() {
super.onResume();
Toast.makeText(this, "onResume", Toast.LENGTH_SHORT).show();
}
@Override
protected void onRestart() {
super.onRestart();
Toast.makeText(this, "onRestart", Toast.LENGTH_SHORT).show();
}
@Override
protected void onPause() {
Toast.makeText(this, "onPause", Toast.LENGTH_SHORT).show();
super.onPause();
}
@Override
protected void onStop() {
Toast.makeText(this, "onStop", Toast.LENGTH_SHORT).show();
super.onStop();
}
@Override
protected void onDestroy() {
Toast.makeText(this, "onDestroy", Toast.LENGTH_SHORT).show();
super.onDestroy();
}
}
由此可见,不少用户操作会导致Activity暂停或杀死,甚至可能启动应用程序的多个版本。在开始新内容之前,有必要提一下另外两个可以控制这种行为的简单技巧。
技巧5:强制采用单任务模式
当跳出一个应用程序然后再次启动它时,可能在设备上产生同一Activity的多个实例。最终多余的实例会被杀死以释放内存,但期间可能引发莫名其妙的情况。为避免这种情况发生,开发者可以在AndroidManifest.xml文件中对每个Activity的此类行为进行控制。
要确保Activity只有一个实例在设备上运行,为拥有MAIN和LAUNCHER两个Intent过滤器的Activity元素内添加如下代码:
android:launchMode="singleInstance"
这样就使任务中的每个Activity始终只有一个实例。此外,任何子Activity都会作为一个单独的任务来启动。为进一步确保应用中的所有Activity都运行在同一个任务中,使用如下代码:
android:launchMode="singleTask"
这样就使多个Activity可以作为同一个任务来轻松地共享信息。
此外,有时我们会希望无论用户以何种方式进入Activity时,都能保持任务状态。例如,如果用户离开了应用,一段时间后又重新启动它,默认的做法通常是将任务重置为初始状态。为保证任务在用户返回时总是被还原到关闭之前的状态,可为任务的根Activity的activity元素指定如下属性:
android:alwaysRetainTaskState="true"
技巧6:强制规定屏幕方向
每个拥有加速度计的Android设备都可以判定哪个方向是向下。当设备从纵向(portrait)模式切换到横向(landscape)模式1 **时,默认的动作是让应用程序的视图也随之旋转。然而,在技巧4中我们看到,Activity会随着屏幕方向的改变而销毁和重启,一旦发生这种事,Activity的当前状态可能丢失,以致干扰用户体验。
处理屏幕变向的办法之一是在改变前保存状态信息,并在改变后予以恢复。更为简单且有用的一种办法是强制屏幕方向保持恒定。可以为AndroidManifest.xml文件中的每一个Activity指定screenOrientation属性。例如,要让Activity始终保持纵向模式,可以在Activity标签内添加如下属性:
android:screenOrientation="portrait"
类似地,若要保持横向模式,则使用下面的代码:
android:screenOrientation="landscape"
然而,光有这样的代码,在硬键盘滑出时仍会引发Activity的销毁和重启。此时,可以采用第三种办法,告诉Android系统:应用程序会处理屏幕变向和键盘滑出事件。可以通过在activity元素中增添如下属性来实现这一点:
android:configChanges="orientation|keyboardHidden"
该属性可以单独使用,也可与screenOrientation属性一起使用,从而向应用程序规定我们想要的行为。
技巧7:保存和恢复Activity信息
当Activity将被杀死时,会调用onSaveInstanceState()函数。可重写该函数以保存有关信息。当Activity被重建时,会调用onRestoreInstanceState()函数,重写它可以恢复保存的信息。这样可以在应用经历生命周期变化时,让用户获得无缝的体验。注意,多数UI状态不需要我们亲自处理,因为默认状况下系统会关照它们。
onSaveInstanceState()有别于onPause(),例如,如果另一组件被启动并置于现有Activity的前端,就会调用onPause()函数。随后,如果当操作系统要回收资源时该Activity仍处于暂停状态,就在结束它之前调用onSaveInstanceState()。
代码清单2-8给出一个保存和恢复实例状态的例子,该状态包含一个字符串和一个float型数组。
代码清单2-8 onSaveInstanceState()和onRestoreInstanceState()的示例
float[] localFloatArray = {3.14f, 2.718f, 0.577f};
String localUserName = "Euler";
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Save the relevant information
outState.putString("name", localUserName);
outState.putFloatArray("array", localFloatArray);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//Restore the relevant information
localUserName = savedInstanceState.getString("name");
localFloatArray = savedInstanceState.getFloatArray("array");
}
注意,onCreate()方法同样包含Bundle savedInstanceState对象。当Activity关闭后又被重新初始化时,在onSaveInstanceState()中被保存的bundle也会被传递到onCreate()方法中。在任何情况下,被保存的bundle都要传递给onSaveInstanceState()函数,所以用它来恢复状态更为自然。
技巧8:使用Fragment
Fragment是新近被加入Android基本构建模块行列的新鲜事物,它们是Activity下属的小部件,用于为视图与功能分组。对Fragment的一个形象类比是把它们想成小的积木块,可以堆在一起从而对更大的积木块进行填充。对小组块的需求源于平板电脑和电视屏幕的引入。
Fragment使得视图可以被捆绑在一起,并按需要被混合并匹配到一个或两个(甚至更多)Activity中去。对Fragment的经典应用是将屏幕从带有一个列表及一个详细视图的横向模式切换到带有一个单列表及一个详细视图的纵向模式。事实上,该模式已变得如此流行,以至于现在可以通过Create New Project对话框直接创建这一模式的应用骨架。
创建的步骤与前面技巧里描述过的类似。
(1)在Eclipse下,选择File → New → Android Application Project。
(2)填写项目名称,比如SimpleFragmentExample。
(3)填写应用程序的名称,比如Example of Basic Fragments。
(4)填写包名称,例如com.cookbook.simplefragments。
(5)将SDK的最低要求选为API Level 11或Android Honeycomb。只有机器上安装了额外的支持库的情况下,Fragment才能在更低的API版本下使用。
(6)在Create Activity界面中选择MasterDetailFlow作为起始点。
(7)为用于演示的项起名,例如叫做f ruits。
(8)点击Finfish按钮完成创建。
进一步探索这一范例功用的工作就留给读者完成。在此我们强调与 Fragment 有关的几点重要事项。
Fragment有它们自己的生命周期,该周期依赖于宿主Activity。由于Fragment可以在Activity生命周期的任意时间点被添加、显示、隐藏和移除,它们比其他组件要更短命一些。与Activity类似,Fragment拥有onPause()、onResume()、onDestroy()和onCreate()方法。
但需要注意的是,对于Fragment,onCreate(Bundle)方法是第二个被调用的方法,第一个调用是onAttach(Activity),它产生信号,表明已存在与宿主Activity的连接。可以在onAttach中调用Activity上的方法,但此时并不能保证Activity已经将自己初始化完毕。只有当调用了onActivityCreated()方法之后,Activity才算是通过了它自己的onCreate()方法。
鉴于Fragment可以在很晚时才被实例化和添加,我们不应依赖Activity在onAttach()中的状态。用于初始化视图并开始大部分工作的乃是onCreateView(LayoutInflater, ViewGroup, Bundle)方法。如果Fragment被是重建的,那么其中的Bundle类为事先保存的实例状态。
Fragment还使用bundle来序列化参数。Fragment所需的每一种属于可打包类型的外部信息,都可以通过调用setArguments()方法从宿主Activity获取。并且总能在Fragment中调用getArguments()方法读取它们。这使得从Activity的起始Intent来的信息能够被直接传递到Fragment中显示。
1在显示方向的英文术语中,纵向被称为portrait(意为肖像),而横向被称为landscape(意为风景),这大概是肖像图多呈纵向(高大于宽),而风景图多呈横向(宽大于高)的缘故。——译者注