android:activity活动的生命周期

掌握活动的生命周期对任何 Android 开发者来说都非常重要,当你深入理解活动的生命 周期之后,就可以写出更加连贯流畅的程序,并在如何合理管理应用资源方面,你会发挥的 游刃有余。你的应用程序将会拥有更好的用户体验。

 

2.4.1    返回栈

 

经过前面几节的学习,我相信你已经发现了这一点,Android 中的活动是可以层叠的。 我们每启动一个新的活动,就会覆盖在原活动之上,然后点击 Back 键会销毁最上面的活动, 下面的一个活动就会重新显示出来。

其实 Android 是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动 的集合,这个栈也被称作返回栈(Back Stack)。栈是一种后进先出的数据结构,在默认情况 下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们 按下 Back 键或调用 finish()方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入 栈的活动就会重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。

示意图 2.19 展示了返回栈是如何管理活动入栈出栈操作的。

 

 

 

图   2.19

 

2.4.2    活动状态

 

每个活动在其生命周期中最多可能会有四种状态。

1.    运行状态

 

 

 

当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。系统最不愿意回收的

就是处于运行状态的活动,因为这会带来非常差的用户体验。

2.    暂停状态 当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。你可

能会觉得既然活动已经不在栈顶了,还怎么会可见呢?这是因为并不是每一个活动都会 占满整个屏幕的,比如对话框形式的活动只会占用屏幕中间的部分区域,你很快就会在 后面看到这种活动。处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这 种活动(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只 有在内存极低的情况下,系统才会去考虑回收这种活动。

3.    停止状态 当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统

仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方 需要内存时,处于停止状态的活动有可能会被系统回收。

4.    销毁状态 当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状

态的活动,从而保证手机的内存充足。

 

2.4.3    活动的生存期

 

Activity 类中定义了七个回调方法,覆盖了活动生命周期的每一个环节,下面我来一一 介绍下这七个方法。

1.    onCreate()

这个方法你已经看到过很多次了,每个活动中我们都重写了这个方法,它会在活动 第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如说加载布 局、绑定事件等。

2.    onStart()

这个方法在活动由不可见变为可见的时候调用。

3.    onResume()

这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的 栈顶,并且处于运行状态。

4.    onPause()

这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方 法中将一些消耗 CPU 的资源释放掉,以及保存一些关键数据,但这个方法的执行速度 一定要快,不然会影响到新的栈顶活动的使用。

5.    onStop()

这个方法在活动完全不可见的时候调用。它和 onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么 onPause()方法会得到执行,而 onStop()方法并不会执行。

6.    onDestroy()

这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。

7.    onRestart()

这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。 以上七个方法中除了 onRestart()方法,其他都是两两相对的,从而又可以将活动分为三种生存期。

1.    完整生存期

活动在 onCreate()方法和 onDestroy()方法之间所经历的,就是完整生存期。一般情 况下,一个活动会在 onCreate()方法中完成各种初始化操作,而在 onDestroy()方法中完 成释放内的操作。

2.    可见生存期

活动在 onStart()方法和 onStop()方法之间所经历的,就是可见生存期。在可见生存 期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两 个方法,合理地管理那些对用户可见的资源。比如在 onStart()方法中对资源进行加载, 而在 onStop()方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。

3.    前台生存期

活动在 onResume()方法和 onPause()方法之间所经历的,就是前台生存期。在前台 生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行相互的,我们平时 看到和接触最多的也这个状态下的活动。

为了帮助你能够更好的理解,Android 官方提供了一张活动生命周期的示意图,如图 2.20

所示。

 

 

 

图   2.20

 

 

2.4.4    体验活动的生命周期

 

讲了这么多理论知识,也是时候该实战一下了,下面我们将通过一个实例,让你可以更 加直观地体验活动的生命周期。

这次我们不准备在 ActivityTest 这个项目的基础上修改了,而是新建一个项目。因此,

首先关闭 ActivityTest 项目,然后新建一个 ActivityLifeCycleTest 项目。新建项目的过程你应 该已经非常清楚了,不需要我再进行赘述,这次我们允许 ADT 帮我们自动创建活动,这样 可以省去不少工作,创建的活动名和布局名都使用默认值。

这样主活动就创建完成了,我们还需要分别再创建两个子活动,NormalActivity 和

DialogActivity,下面一步步来实现。

新建 normal_layout.xml 文件,代码如下所示:

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"  android:layout_height="match_parent" android:orientation="vertical" >

<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="This is a normal activity"/>

</LinearLayout>

这个布局中我们就非常简单地使用了一个 TextView,用于显示一行文字,在下一章中你 将会学到更多关于 TextView 的用法。然后同样的方法,我们再新建一个 dialog_layout.xml 文件,代码如下所示:

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" >

<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="This is a dialog activity"/>

</LinearLayout>

两个布局文件的代码几乎没有区别,只是显示的文字不同而已。 然后新建 NormalActivity 继承自 Activity,代码如下所示:

public class NormalActivity extends Activity {

@Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE);

setContentView(R.layout.normal_layout);

}

}

我们在 NormalActivity 中加载了 normal_layout 这个布局。 同样的方法,再新建 DialogActivity 继承自 Activity,代码如下所示:

public class DialogActivity extends Activity {

 

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE);

setContentView(R.layout.dialog_layout);}

}

我们在 DialogActivity 中加载了 dialog_layout 这个布局。 其实从名字上你就可以看出,这两个活动一个是普通的活动,一个是对话框式的活动。

可是现在不管怎么看,这两个活动的代码都几乎都是一模一样的,在哪里有体现出将活动设 成对话框式的呢?别着急,下面我们马上开始设置。在 AndroidManifest.xml 的<activity>标 签中添加如下代码:

 

<activity android:name=".NormalActivity" >

</activity>

<activity android:name=".DialogActivity" android:theme="@android:style/ Theme.Dialog" >

</activity>

这里分别为两个活动进行注册,但是 DialogActivity 的注册代码有些不同,它使用了一 个 android:theme 属性,这是用于给当前活动指定主题的,Android 系统内置有很多主题可以 选择,当然我们也可以定制自己的主题,而这里@android:style/Theme.Dialog 则毫无疑问是 让 DialogActivity 使用对话框式的主题。

 

接下来我们修改 activity_main.xml,重新定制我们主活动的布局:

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" >

<Button android:id="@+id/start_normal_activity" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Start NormalActivity" />

<Button android:id="@+id/start_dialog_activity" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Start DialogActivity" />

</LinearLayout>

自动生成的布局代码有些复杂,这里我们完全替换掉,仍然还是使用最熟悉的 LinearLayout, 然后加入了两个按钮,一个用于启动 NormalActivity,一个用于启动 DialogActivity。

最后修改 MainActivity 中的代码,如下所示:

 

public class MainActivity extends Activity {

 

public static final String TAG = "MainActivity";

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main);

Button startNormalActivity = (Button) findViewById(R.id.start_

normal_activity);

Button startDialogActivity = (Button) findViewById(R.id.start_

dialog_activity);

startNormalActivity.setOnClickListener(new OnClickListener() {

@Override

 public void onClick(View v) {

Intent intent = new Intent(MainActivity.this, NormalActivity.class);

startActivity(intent);

}

});

startDialogActivity.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

Intent intent = new Intent(MainActivity.this, DialogActivity.class);

startActivity(intent);

}

});

}

 

@Override

protected void onStart() {

super.onStart(); Log.d(TAG, "onStart");

}

 

@Override

protected void onResume() {

super.onResume(); Log.d(TAG, "onResume");

}

 

@Override

protected void onPause() {

super.onPause(); Log.d(TAG, "onPause");

}

 

@Override

protected void onStop() {

super.onStop(); Log.d(TAG, "onStop");

}

 

@Override

protected void onDestroy() {

super.onDestroy(); Log.d(TAG, "onDestroy");

}

 

@Override

protected void onRestart() {

super.onRestart(); Log.d(TAG, "onRestart");

}

 

}

在 onCreate()方法中,我们分别为两个按钮注册了点击事件,点击第一个按钮会启动 NormalActivity,点击第二个按钮会启动 DialogActivity。然后在 Activity 的七个回调方法中 分别打印了一句话,这样就可以通过观察日志的方式来更直观地理解活动的生命周期。

现在运行程序,效果如图 2.21 所示。

 

 

 

图   2.21

 

 

 

这时观察 LogCat 中的打印日志,如图 2.22 所示。

 

 

 

图   2.22

 

可以看到,当 MainActivity 第一次被创建时会依次执行 onCreate()、onStart()和 onResume()

方法。然后点击第一个按钮,启动 NormalActivity,如图 2.23 所示。

 

 

 

图   2.23

 

此时的打印信息如图 2.24 所示。

 

 

 

图   2.24

 

 

 

由于 NormalActivity 已经把 MainActivity 完全遮挡住,因此 onPause()和 onStop()方法都

会得到执行。然后按下 Back 键返回 MainActivity,打印信息如图 2.25 所示。

 

 

图   2.25

 

由于之前 MainActivity 已经进入了停止状态,所以 onRestart()方法会得到执行,之后又 会依次执行 onStart()和 onResume()方法。注意此时 onCreate()方法不会执行,因为 MainActivity 并没有重新创建。

然后再点击第二个按钮,启动 DialogActivity,如图 2.26 所示。

 

 

 

图   2.26

 

此时观察打印信息,如图 2.27 所示。

 

 

图   2.27

 

 可以看到,只有 onPause() 方法得到了执行,onStop() 方法并没有执行,这是因为

DialogActivity 并没有完全遮挡住 MainActivity,此时 MainActivity 只是进入了暂停状态,并 没有进入停止状态。相应地,按下 Back 键返回 MainActivity 也应该只有 onResume()方法会 得到执行,如图 2.28 所示。

 

 

 

图   2.28

 

最后在 MainActivity 按下 Back 键退出程序,打印信息如图 2.29 所示。

 

 

 

图   2.29

 

依次会执行 onPause()、onStop()和 onDestroy()方法,最终销毁 MainActivity。 这样活动完整的生命周期你已经体验了一遍,是不是理解得更加深刻了?

 

2.4.5   活动被回收了怎么办

 

前面我们已经说过,当一个活动进入到了停止状态,是有可能被系统回收的。那么想象 以下场景,应用中有一个活动 A,用户在活动 A 的基础上启动了活动 B,活动 A 就进入了 停止状态,这个时候由于系统内存不足,将活动 A 回收掉了,然后用户按下 Back 键返回活 动 A,会出现什么情况呢?其实还是会正常显示活动 A 的,只不过这时并不会执行 onRestart() 方法,而是会执行活动 A 的 onCreate()方法,因为活动 A 在这种情况下会被重新创建一次。 这样看上去好像一切正常,可是别忽略了一个重要问题,活动 A 中是可能存在临时数据 和状态的。打个比方,MainActivity 中有一个文本输入框,现在你输入了一段文字,然后

启动 NormalActivity,这时 MainActivity 由于系统内存不足被回收掉,过了一会你又点击了 Back 键回到 MainActivity,你会发现刚刚输入的文字全部都没了,因为 MainActivity 被重新 创建了。

如果我们的应用出现了这种情况,是会严重影响用户体验的,所以必须要想想办法解决 这个问题。查阅文档可以看出,Activity 中还提供了一个 onSaveInstanceState()回调方法,这 个方法会保证一定在活动被回收之前调用,因此我们可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。

onSaveInstanceState()方法会携带一个 Bundle 类型的参数,Bundle 提供了一系列的方法 用于保存数据,比如可以使用 putString()方法保存字符串,使用 putInt()方法保存整型数据, 以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从 Bundle 中取值, 第二个参数是真正要保存的内容。

在 MainActivity 中添加如下代码就可以将临时数据进行保存:

 

@Override

protected void onSaveInstanceState(Bundle outState) {

super.onSaveInstanceState(outState);

String tempData = "Something you just typed";

outState.putString("data_key", tempData);

}

数据是已经保存下来了,那么我们应该在哪里进行恢复呢?细心的你也许早就发现,我 们一直使用的 onCreate()方法其实也有一个 Bundle 类型的参数。这个参数在一般情况下都是 null,但是当活动被系统回收之前有通过 onSaveInstanceState()方法来保存数据的话,这个参 数就会带有之前所保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可。

修改 MainActivity 的 onCreate()方法,如下所示:

 

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main);

if (savedInstanceState != null) {

String tempData = savedInstanceState.getString("data_key"); Log.d(TAG, tempData);

}

……

}

取出值之后再做相应的恢复操作就可以了,比如说将文本内容重新赋值到文本输入框 上,这里我们只是简单地打印一下。

不知道你有没有察觉,使用 Bundle 来保存和取出数据是不是有些似曾相识呢?没错! 我们在使用 Intent 传递数据时也是用的类似的方法。这里跟你提醒一点,Intent 还可以结合 Bundle 一起用于传递数据的,首先可以把需要传递的数据都保存在 Bundle 对象中,然后再 将 Bundle 对象存放在 Intent 里。到了目标活动之后先从 Intent 中取出 Bundle,再从 Bundle中一一取出数据。

posted @ 2015-12-30 13:49  dodo-yufan  阅读(4555)  评论(0编辑  收藏  举报