Activity 的 "生命周期"

1、活动状态

每个活动在其生命周期中最多可能会有 4 种状态:运行状态、暂停状态、停止状态、销毁状态

1.1 运行状态

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

1.2 暂停状态

当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了“暂停状态”。你可能会觉得既然活动已经不在栈顶了,还怎么会可见呢?

这是因为并不是每一个活动都会占满整个屏幕,比如对话框形式的活动只会占用屏幕中间的部分区域。处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动,只有在内存极低的情况下,系统才会去考虑回收这种活动。

1.3 停止状态

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

1.4 销毁状态

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


2、回调方法

Activity 类中定义了 7 个回调方法,覆盖了 Activity 生命周期的每一个环节:

2.1 onCreate()

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

2.2 onStart()

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

2.3 onResume()

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

2.4 onPause()

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

2.5 onStop()

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

2.6 onDestroy()

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

2.7 onRestart()

这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。


3、生存期

以上 7 个方法中除了 onRestart() 方法,其他都是两两对应的,从而可以将活动分为 3 种生存期。

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

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

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

我们看下 Activity 生命周期的示意图:

mZ9KiQ.png


4、代码案例

4.1 Code

我们先定义一个:NormalActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".NormalActivity">

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

再定义一个:DialogActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DialogActivity">

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

为了让 DialogActivity 使用对话框式主题,我们在 AndroidManifest.xml 中做如下设置:

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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.marco.activitylifecycletest">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".NormalActivity" />
<activity android:name=".DialogActivity"
android:theme="@style/Theme.AppCompat.Dialog"> // 对话框主题
</activity>
</application>

</manifest>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<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>

修改 MainActivity :

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
package com.example.marco.activitylifecycletest;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

public static final String TAG = "MainActivity";
private Button startNormalActivity = null;
private Button startDialogActivity = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startNormalActivity = findViewById(R.id.start_normal_activity);
startDialogActivity = findViewById(R.id.start_dialog_activity);
startNormalActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, NormalActivity.class);
startActivity(intent);
}
});
startDialogActivity.setOnClickListener(new View.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");
}
}

4.2 Result

(1)当 MainActivity 第一次被创建时,如下方法被执行:

1
2
3
22:31:29.071 2526-2526/? D/MainActivity: onCreate
22:31:29.077 2526-2526/? D/MainActivity: onStart
22:31:29.083 2526-2526/? D/MainActivity: onResume

(2)点击 Start NormalActivity 按钮:

1
2
22:33:02.159 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onPause
22:33:02.745 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onStop

因为 NormalActivity 已经把 MainActivity 完全遮挡住,因此 onPause()onStop() 方法都会得到执行。

(3)点击 Back 键返回 MainActivity:

1
2
3
22:35:00.010 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onRestart
22:35:00.012 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onStart
22:35:00.014 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onResume

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

(4)点击 Start DialogActivity 按钮:

1
22:43:38.006 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onPause

通过 Log 可以看到,只有 onPause() 方法得到了执行,onStop() 方法并没有执行,这是因为 DialogActivity 并没有完全遮挡住 MainActivity,此时 MainActivity 只是进入了暂停状态,并没有进入停止状态。

(5)点击 Back 键返回 MainActivity:

1
22:50:12.222 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onResume

按下 Back 键返回 MainActivity 也应该只有 onResume() 方法会得到执行。

(6)在 MainActivity 界面按下 Back 键退出程序:

1
2
3
22:51:47.673 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onPause
22:51:48.013 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onStop
22:51:48.015 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onDestroy

依次会执行 onPause()onStop()onDestroy(),最终销毁 MainActivity。


5、疑问

我们在之前分析 Activity 的生命周期的时候曾经提到过:如果一个活动进入了 onStop (停止) 状态,是有可能被系统回收的!

5.1 场景

比如我们看以下的场景:

应用中有一个活动 A ,用户在活动 A 的基础上启动了活动 B ,活动 A 就进入了停止的状态,这个时候由于系统内存不足,将活动 A 回收掉了,然后用户按下 Back 键返回活动 A ,会出现什么情况呢?

其实还是会正常显示活动 A 的,但是此时并不会执行 onRestart() 方法了,而是会执行活动 A 的 onCreate() 方法,因为活动 A 在这种情况下会被重新创建一次。

可能这并不会影响正常的功能,但是存在一个特殊情况:如果活动 A 中存在临时数据和状态(比如 A 中有一个文本输入框,我们输入了一些文字,然后启动了 B 活动,如果 A 被 kill了,在重新回到 A 后,A 活动重新创建,那么数据都丢失了),此时会严重影响用户体验,此时该怎么办?

5.2 策略

其实官方文档给出了解决方案,Activity 中提供了一个 onSaveInstanceState() 回调方法,这个方法可以保证在活动被回收之前一定会被调用,因此我们可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。

onSaveInstanceState() 方法会携带一个 Bundle 类型的参数,Bundle 提供了一系列的方法用于保存数据,比如可以使用 putString() 方法保存字符串,使用 putInt() 方法保存整型数据,依次类推。

每个保存方法需要传入两个参数,第一个参数是键,用于后面从 Bundle 中取值,第二个参数是真正要保存的内容。

5.3 Code

我们现在对上面的代码进行修改,在 MainActivity 中添加如下代码将临时数据进行保存:

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);
}

Ok,数据保存好了,那我们应该在哪边进行恢复?

不知道你有没有发现,在 onCreate() 方法中有一个 Bundle 类型的参数。这个参数一般情况下是 null ,但是如果在活动被系统回收之前有通过 onSaveInstanceState() 方法来保存数据的话,这个参数就会带有之前所保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可。

修改 MainActivity 的 onCreate() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "onCreate");

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

通过上面的方法取出值之后再做相应的恢复操作就可以了,比如说将文本内容重新赋值到文本输入框上即可。