浅谈Activity的生命周期和启动模式

前言

距离上一篇文章,过去有半个多月了,在此期间忙于工作,疏于整理和总结,特此写下这篇博文,来谈谈自己对Activity的理解。总所周知,Activity组件在Android中的重要性不言而喻,我们所能看到的交互动作离不开活动,我们能看到的界面也离不开活动,那么我想从以下几个方面来谈谈:

  • 什么是活动(Activity)

  • 活动(Activity)用法

  • 活动(Activity)生命周期

  • 活动(Activity)启动模式

  • 活动(Activity)管理

什么是活动(Activity)

 活动(Activity)是最容易吸引用户的地方了,它是一种可以包含用户界面的组件,主要用于和用户进行交互,一个应用程序可以包含零个或者多个活动。这是我对活动的理解,那我们再看看官方文档是怎么定义的:“An Activity is an application component that provides a screen with which users can interact in order to do something, such as dial the phone, take a photo, send an email, or view a map.”大概意思是说,Activity是一个可以让屏幕提供用户交互动作的组件,比如打电话、照相、发送邮件和查看地图等。简单来说,我们在应用程序中能看到的内容,绝大多数都是Activity组件提供的。

活动(Activity)用法

    在一个应用程序中,有多个Activity组成,其中,有一个特殊的Activity,那就是“main”Activity,代表了应用程序启动,看到的首个界面。每个Activity启动其他的Activity可以通过不同动作。接下来我们来创建一个Activity。

创建一个Activity

为了创建一个Activity,我们必须要继承Activity的子类,比如:public class MainActivity extends Activity。同时我们也要重载两个方法onCreate()、onPause(),代码如下:

  img

 其中onCreate()方法在你的activity创建的时候就调用了,你可以在这个方法中初始化一些常量、资源的连接,其中最重要的是setContentView()方法去加载活动界面的布局。onPause()方法往往是当你活动交互暂停时,一些在界面上填过的交互数据可以在这里保存,以免造成丢失,用户体验不好。

声明活动

Activity在manifest注册,除了要定义和继承Activity之外,我们还需要在manifest中进行注册,表示声明,如果不声明的话,这个Activity在运行时候,会报ANR错误,就是应用程序没有响应。那么怎么声明呢,我们每新建一个项目时候,都会有一个AndroidManifest.xml文件,我们需要在AndroidManifest.xml文件中对我们的Activity进行注册。如图

img

可以看到每一个应用程序,都必须有一个AndroidManifest.xml。

img
可以看到,活动的注册在标签中,这里通过标签来对活动进行注册的,首先我们使用了android:name属性来具体注册哪一个活动,在这里我们对我们刚才定义的MainActivity类进行注册,由于在最外层的标签中已经通过package属性指定了包名是com.example.helloworld,因此在注册活动时这一部分就可以省略了,当然你可以定义为com.example.helloworld.MainActivity,不过直接使用.MainActivity也就足够了,然后我们使用了android:label指定活动中的标题栏的内容,标题栏是显示在活动的最顶部的,需要注意的是,给主活动指定的label不仅会成为标题栏的内容,还会成为启动器(Launcher)中应用程序显示的名称。之后在标签的内容我们加入了标签,并在这个标签里添加了这两句声明。表示在手机上点击应用图标时,首先启动的就是这个活动,如果你的应用程序中没有声明任何一个活动作为主活动,这个程序仍然是可以正常安装的,只是你无法在启动中看到或者打开这个程序。这种没有主活动的应用程序,基本都是用为第三方服务居多,比如支付宝快捷支付服务。

基本上以上两个步骤的结合,这个活动就能在应用程序中运行了。

活动(Activity)生命周期

 在说说Activity生命周期之前,我们先来理解”Activity返回栈”的概念。在Android中,多个Activity定义在一个Task中,也就是说一个Task是一组Activity的集合,然后Activity又被安排在back stack,即返回栈,是按照后进先出的规则进出栈的。如图:

img

当从手机中启动一个应用程序图标时,应用程序的Task随之也变成前台进程,如果首次启用时,发现不存在Task实例,那么系统会创建一个Task实例,然后把”main”Activity放入到stack中,默认是栈顶,这样一个Task就管理了一组栈,栈里管理多个Activity。当从Activity1中启动Activity2时,Activity1被推到栈底,Activity2变成栈顶,同理,Activity3进栈的过程和Activity2是一样的,如果我们按back按钮,则Activity3被弹出,系统会根据内存情况进行销售或者回收,Activity2则被推到栈顶,以此类推。当一直按back,返回到主页面,则所有的activitys被全部弹出,则task不复存在。

同时,task有两种状态:Foreground和Background,前景和背景。当处于Background时,所有的activitys是停止的,当处于Foreground时,表示当前用户交互的应用程序。 

 img 

比如两个应用程序A和B,我们刚开始启动了A应用程序,此时Task A是Foreground,用于与用户交互,当我们点击Home Button时,此时Task A变成Background,里面所有的activitys都是停止的,此时如果我们又启动了B应用程序,Task B又会被实例化,Task B变成Foreground,不过Task A仍然是Background,一直等待着被恢复。所以是Android也是一个多任务的系统,不同任务是可以被互相切换的。

好了关于Task和Stack就先了解这么多,接下来讲讲Activity的生命周期。每个活动在其生命周期中最多可能会有四种状态

  • 运行状态
  • 暂停状态
  • 停止状态
  • 销毁状态

运行状态
当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验。

暂停状态
当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。比如对话框的形式只会占用屏幕中间的部分区域,你很快就会在后面看到这种活动,处于暂停状态的活动仍然是完全存活的,系统也不愿意去回收这种活动,只有在内存极低的情况下,系统才会去考虑回收这种活动。

停止状态
当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。

销毁状态
当一个活动从返回栈中移除后就变成了销毁状态。系统会倾向于回收处于这种状态的活动,从而保证手机的内存充足。

七个方法
Activity类中定义了七个回调方法,覆盖了活动生命周期的每一个环节。有如下七个方法:

  • onCreate()
  • onStart()
  • onResume()
  • onPause()
  • onStop()
  • onDestroy()
  • onRestart()

onCreate():每个活动中我们都会重写这个方法,它会在活动第一次被创建的时候调用,比如完成一些初始化操作,加载布局、绑定事件等
onStart():这个方法在活动不可见变为可见的时候调用
onResume():这个方法在活动准备好和用户进行交互的时候调用,此时的活动一定位于返回栈的栈顶、并且处于运行状态。
onPause():这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。
onStop():这个方法在活动完全不可见的时候调用。它和onPause()方法主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法不会执行。
onDestroy():这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。
onRestart():这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了

除此之外,活动又可以分为三种生存期

  • 完整生存期:活动在onCreate()和onDestroy()方法之间所经历的
  • 可见生存期:活动在onStart()和onStop()方法之间所经历的
  • 交互活动期:活动在onResume()和onPause()方法之间所经历的

img

让我们用代码来体验下Activity的生命周期,首先我们先创建一个类来继承Activity父类,用日志来打印出活动的七个回调方法。定义了两个按钮,一个是启动正常的Activity,一个启动对话框的Activity,布局界面代码就不给出了,然后编写点击事件代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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);
if (savedInstanceState != null) {
String tempData = savedInstanceState.getString("data_key");
Log.d(TAG, tempData);
}
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");
}

@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Something you just typed";
outState.putString("data_key", tempData);
}
}

首先启动应用程序,默认进来的就是MainActivity界面,点击运行:观察LogCat中的打印日志

img

可以看到,当MainActivity第一次被运行的时候,会依次执行onCreate()、onStart()和onResume()方法。然后点击正常启动另外一个Activity按钮,观察LogCat日志如下:

img

由于MainActivity完全被另一个Activity覆盖了,因此也执行了onPause()和onStop()方法。
接着我们从另外一个Activity返回到MainActivity界面,即按下返回按钮,此时观察的LogCat日志如下:

img

会发现由于之前MainActivity已经进入了停止状态,所以onRestart()方法会得到执行,之后有会依次执行onStart()和onResume()方法。此时要注意,onCreate()方法不会执行,因为MainActivity并没有重新创建。然后我们点击启动对话框的按钮,观察LogCat日志如下:

img
发现只有onPause()方法得到执行,onStop()方法并没有执行,这是因为对话框的Activity并没有完全遮挡住MainActivity,此时MainActivity只是进入了暂停状态,并没有进入停止状态。相应的,按下返回键返回MainActivity也应该只有onResume()方法得到执行。

img
最后在MainActivity按下返回键,退出程序,活动当然会被销售,执行onDestroy()方法。
img

 

活动被回收的情况
前面说过了,当一个活动进入到停止状态,是有可能被系统回收的,如果我上次执行的临时数据和状态存在,则被回收的话,相当于临时数据被清空了,上次刚刚输入的文字全部都没了,那样的用户体验真是糟糕啊。所以Activity中还提供了一个onSaveInstanceState()回调方法,这个方法会保证一定在活动被回收之前调用,因此我们可以通过这个方法来解决会动被回收时临时数据得不到保存的情况。

1
2
3
4
5
6
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Something you just typed";
outState.putString("data_key", tempData);
}

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

1
2
3
4
if (savedInstanceState != null) {	
String tempData = savedInstanceState.getString("data_key");
Log.d(TAG, tempData);
}

活动(Activity)启动模式

    每个活动(Activity)都有一个相应的启动模式,启动模式一共有四种,分别是standard、singleTop、singleTask、singleInstance,可以在AndroidManifest.xml中通过给标签指定android:launchMode属性来选择启动模式。

standard
 standard是活动默认的启动模式,在不进行显示指定的情况下,所有活动都会自动使用这种启动模式。对于返回栈,在standard模式下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。

   img

singleTop

除了有standard模式之外,还存在一种叫做singleTop的模式,当活动的启动模式指定为singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
img

singleTask
使用singleTop模式很好地解决重复创建栈活动的问题,可是如果该活动并没有处于栈顶的位置,还是可能会创建多个活动实例的。如果想要让某个活动在整个应用程序的上下文中只存在一个实例呢?可以使用singleTask模式来启动。当活动的启动模式指定为singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
img

singleInstance
singleInstance模式不同以上三个模式,指定为singleInstance模式的活动会启用一个新的返回栈来管理这个活动。通常应用以下场景,假设我们的程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例,那么该如何实现呢?使用前面三种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用了singleInstance模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。
img

活动(Activity)管理

一般我们在日常开发中,基本上会有一个专门的集合类对所有活动进行管理,这样的做的好处是可以保证在退出应用时,活动也可以随时的释放。我们需要定义一个专门管理活动的父类,然后让每个自定义的Activity子类来继承父类,代码如下: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class BaseActivity extends Activity{

private boolean allowFullScreen=true; //是否允许全屏
private boolean allowDestroy=true; //是否允许销毁

@SuppressWarnings("unused")
private View view;

public boolean isAllowFullScreen() { //获取是否全屏
return allowFullScreen;
}

public void setAllowFullScreen(boolean allowFullScreen) { //设置是否全屏
this.allowFullScreen = allowFullScreen;
}

public boolean isAllowDestroy() { //获取是否销毁
return allowDestroy;
}

public void setAllowDestroy(boolean allowDestroy) { //设置是否销毁
this.allowDestroy = allowDestroy;
}

public void setAllowDestroy(boolean allowDestroy, View view) { //设置是否销毁(重载)
this.allowDestroy = allowDestroy;
this.view = view;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
allowFullScreen = true;
AppManager.getAppManager().addActivity(this); //添加Activity到堆栈
}

@Override
protected void onStop() {
super.onStop();
}

@Override
protected void onStart() {
super.onStart();
}

@Override
protected void onRestart() {
super.onRestart();
}

@Override
protected void onResume() {
super.onResume();
}

@Override
protected void onPause() {
super.onPause();
}

@Override
protected void onDestroy() {
AppManager.getAppManager().finishActivity(this); // 结束Activity&从堆栈中移除
super.onDestroy();
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) { //监控返回键,并且包含视图组件
if (!allowDestroy) {
return false;
}
}
return super.onKeyDown(keyCode, event);
}

,