Android 系统开发_四大组件篇 -- 探讨 Activity 的生命周期

2019-03-27 11:22:51 浏览数 (1)

活动状态

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

1、运行状态

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

2、暂停状态

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

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

3、停止状态

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

4、销毁状态

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

回调方法

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

onCreate()

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

onStart()

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

onResume()

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

onPause()

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

onStop()

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

onDestroy()

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

onRestart()

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

在这由于文字很多,我总结了关于Android一系列的学习资料及思维导图免费分享给大家,文末有领取!

生存期

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

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

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

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

我们看下官方给出的 Activity 生命周期的示意图:

image

实战演练

Code

我们先定义一个:NormalActivity

代码语言:javascript复制
<?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>

image.gif

再定义一个:DialogActivity

代码语言:javascript复制
<?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>

image.gif

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

代码语言:javascript复制
<?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">    // Look here
        </activity>
    </application>

</manifest>

image.gif

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

代码语言:javascript复制
<?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>

image.gif

修改 MainActivity :

代码语言:javascript复制
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");
    }
}

image.gif

Result

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

代码语言:javascript复制
2018-10-18 04:31:29.071 2526-2526/? D/MainActivity: onCreate
2018-10-18 04:31:29.077 2526-2526/? D/MainActivity: onStart
2018-10-18 04:31:29.083 2526-2526/? D/MainActivity: onResume

image.gif

(2)点击 Start NormalActivity 按钮:

代码语言:javascript复制
2018-10-18 04:33:02.159 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onPause
2018-10-18 04:33:02.745 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onStop

image.gif

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

(3)点击 Back 键返回 MainActivity:

代码语言:javascript复制
2018-10-18 04:35:00.010 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onRestart
2018-10-18 04:35:00.012 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onStart
2018-10-18 04:35:00.014 2526-2526/com.example.marco.activitylifecycletest D/MainActivity: onResume

image.gif

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

(4)点击 Start DialogActivity 按钮:

代码语言:javascript复制
2018-10-18 04:43:38.006 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onPause

image.gif

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

(5)点击 Back 键返回 MainActivity:

代码语言:javascript复制
2018-10-18 04:50:12.222 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onResume

image.gif

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

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

代码语言:javascript复制
2018-10-18 04:51:47.673 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onPause
2018-10-18 04:51:48.013 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onStop
2018-10-18 04:51:48.015 3555-3555/com.example.marco.activitylifecycletest D/MainActivity: onDestroy

image.gif

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

疑问

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

场景

比如我们看以下的场景:

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

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

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

策略

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

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

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

Code

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

代码语言:javascript复制
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        String tempData = "Something you just typed";
        outState.putString("data_key", tempData);
    }

image.gif

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

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

修改 MainActivity 的 onCreate() 方法:

代码语言:javascript复制
    @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);
        }
        ... ...
    }

image.gif

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

0 人点赞