【Android】Activity的生命周期

Activity的生命周期

1.返回栈

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

2.Activity状态

每个Activity在其生命周期中最多可能会有4种状态。

Android Activity 的生命周期是其运行过程中所经历的一系列状态。每个 Activity 在其生命周期中可能会经历以下几种状态:

  1. 活跃状态(运行状态)(Active or Running) :
    1. 这是 Activity 的正常运行状态。它处于用户界面的最顶层,并且能够接收用户的输入。
    2. 系统最不愿意回收的就是处于运行状态的Activity,因为这会带来非常差的用户体验。
  2. 暂停状态(Paused) :
    1. 当一个新的 Activity 部分遮挡了当前 Activity,或者当前 Activity 调用了 sleep 方法时,它就会进入暂停状态。在这种状态下,Activity 仍然可见,但是无法接收用户输入。
    2. 当一个Activity不再处于栈顶位置,但仍然可见时,Activity就进入了暂停状态。你可能会觉得,既然Activity已经不在栈顶了,怎么会可见呢?这是因为并不是每一个Activity都会占满整个屏幕,比如对话框形式的Activity只会占用屏幕中间的部分区域。处于暂停状态的Activity仍然是完全存活着的,系统也不愿意回收这种Activity(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只有在内存极低的情况下,系统才会去考虑回收这种Activity。
  3. 停止状态(Stopped) :
    1. Activity 被另一个 Activity 完全遮挡时,它就会进入停止状态。在这种状态下,Activity 不再可见,但是它的状态和成员信息仍然被保留。
    2. 系统仍然会为这种Activity保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的Activity有可能会被系统回收。
  4. 非可见状态(销毁状态)(Invisible) :
    1. 当系统需要为 Activity 显示一个透明的活动,如对话框,或者 Activity 调用了 finish() 方法后,它就会进入非可见状态。在这种状态下,Activity 对用户完全不可见,系统可能会随时将其销毁
    2. 一个Activity从返回栈中移除后就变成了销毁状态。系统最倾向于回收处于这种状态的Activity,以保证手机的内存充足。

3.Activity的生存期

  1. onCreate(Bundle savedInstanceState) :
    1. 这是 Activity 生命周期中的第一个回调方法,用于进行 Activity 的初始化工作。当 Activity 第一次创建时被调用。
    2. 您可以在这里执行一次性的初始化操作,如设置布局和恢复保存的状态。
  2. onStart() :
    1. Activity 从停止状态变为可见状态时调用。
    2. 此时 Activity 对用户可见,但还没有进入前台。
  3. onResume() :
    1. Activity 进入前台并开始与用户交互时调用。这是 Activity 运行时的活跃状态,可以执行UI更新和用户交互。
  4. onPause() :
    1. Activity 部分被另一个 Activity 遮挡,即将失去焦点时调用。这是一个保存当前 Activity 状态的好时机,例如保存用户输入或应用状态。
    2. 我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶Activity的使用。
  5. onStop() :
    1. Activity 不再可见时调用。这可能是因为另一个 Activity 完全覆盖了它,或者 Activity 调用了 finish() 方法。在这个方法中,您应该释放不需要的资源。
  6. onRestart() :
    1. Activity 从停止状态恢复到可见状态(运行状态)时调用。这通常发生在 Activity 被另一个 Activity 短暂覆盖后。
  7. onDestroy() :
    1. Activity 被销毁时调用。这是一个释放资源和取消注册广播接收器、停止服务等操作的好时机。
  8. onSaveInstanceState(Bundle outState) :
    1. 这个方法在 Activity 被系统销毁之前调用,但 Activity 被销毁并不是由于用户操作,如屏幕旋转或配置更改。您可以在这里保存 Activity 的状态,这些状态将在 Activity 重建时通过 onCreate(Bundle savedInstanceState) 方法传递。
  1. 创建(Creation) :
    1. 这个阶段由 onCreate(Bundle savedInstanceState) 方法开始,表示 Activity 正在被创建。如果 Activity 由于配置更改(如屏幕旋转)被系统销毁并重新创建,savedInstanceState 参数将携带之前的状态信息。这个阶段通常用于初始化 Activity,设置布局和恢复状态。
  2. 运行(Active) :
    1. 这个阶段包括 onStart()onResume() 两个回调方法。onStart() 表示 Activity 变得可见,但用户可能还不能与之交互。紧接着,onResume() 表示 Activity 已经准备好与用户交互,它现在处于前台并且是活跃状态。
  3. 暂停(Pause) :
    1. 这个阶段由 onPause()onStop() 两个回调方法组成。onPause() 表示 Activity 即将失去焦点,但用户不应该在这个时刻保存 Activity 的持久状态,因为这个方法可能会被频繁调用。紧接着,onStop() 表示 Activity 已经不可见,您可以在这个时刻释放不需要的资源。

onRestart() 方法是特殊的,因为它既不与创建阶段对应,也不与运行或暂停阶段对应。它在 Activity 从停止状态变为可见状态时被调用,通常发生在 Activity 被另一个 Activity 短暂覆盖后,或者当 Activity 被系统销毁并需要重新启动时。

4.Activity被回收了怎么办

前面我们说过,当一个Activity进入了停止状态,是有可能被系统回收的。那么想象以下场景:应用中有一个Activity A,用户在Activity A的基础上启动了Activity B,Activity A就进入了停止状态,这个时候由于系统内存不足,将Activity A回收掉了,然后用户按下Back键返回Activity A,会出现什么情况呢?其实还是会正常显示Activity A的,只不过这时并不会执行onRestart()方法,而是会执行Activity A的onCreate()方法,因为Activity A在这种情况下会被重新创建一次。

这样看上去好像一切正常,可是别忽略了一个重要问题:Activity A中是可能存在临时数据和状态的。打个比方,MainActivity中如果有一个文本输入框,现在你输入了一段文字,然后启动NormalActivity,这时MainActivity由于系统内存不足被回收掉,过了一会你又点击了Back键回到MainActivity,你会发现刚刚输入的文字都没了,因为MainActivity被重新创建了。

如果我们的应用出现了这种情况,是会比较影响用户体验的,所以得想想办法解决这个问题。其实,Activity中还提供了一个onSaveInstanceState()回调方法,这个方法可以保证在Activity被回收之前一定会被调用,因此我们可以通过这个方法来解决问题。

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

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

数据是已经保存下来了,那么我们应该在哪里进行恢复呢?

细心的你也许早就发现,我们一直使用的onCreate()方法其实也有一个Bundle类型的参数。这个参数在一般情况下都是null,但是如果在Activity被系统回收之前,你通过onSaveInstanceState()方法保存数据,这个参数就会带有之前保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可。

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

Java 复制代码
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(tag, "onCreate");
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null) {
        String tempData = savedInstanceState.getString("data_key");
        Log.d(tag, "tempData is " + tempData);
    }
}

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

8.Activity的启动模式

在实际项目中我们应该根据特定的需求为每个Activity指定恰当的启动模式。

启动模式一共有4种,分别是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通过给标签指定android:launchMode属性来选择启动模式。下面我们来逐个进行学习。

standard

standard是Activity默认的启动模式,在不进行显式指定的情况下,所有Activity都会自动使用这种启动模式。到目前为止,我们写过的所有Activity都是使用的standard模式。

经过上一节的学习,你已经知道了Android是使用返回栈来管理Activity的,在standard模式下,每当启动一个新的Activity,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的Activity,系统不会在乎这个Activity是否已经在返回栈中存在,每次启动都会创建一个该Activity的新实例。我们现在通过实践来体会一下standard模式,这次还是在ActivityTest项目的基础上修改。

首先关闭ActivityLifeCycleTest项目,打开ActivityTest项目。修改FirstActivity中onCreate()方法的代码,如下所示:

Java 复制代码
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("FirstActivity", this.toString());
    setContentView(R.layout.first_layout);

    Button button1 = findViewById(R.id.button1); // 确保你的按钮ID是button1
    button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
            startActivity(intent);
        }
    });
}

singleTop

可能在有些情况下,你会觉得standard模式不太合理。Activity明明已经在栈顶了,为什么再次启动的时候还要创建一个新的Activity实例呢?别着急,这只是系统默认的一种启动模式而已,你完全可以根据自己的需要进行修改,比如使用singleTop模式。当Activity的启动模式指定为singleTop,在启动Activity时如果发现返回栈的栈顶已经是该Activity,则认为可以直接使用它,不会再创建新的Activity实例。

我们还是通过实践来体会一下,修改AndroidManifest.xml中FirstActivity的启动模式,如下所示:

Java 复制代码
    <application ...>
        <activity android:name=".FirstActivity"
            android:launchMode="singleTop"
            android:label="This is FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!-- 其他活动和组件 -->
    </application>

但是之后不管你点击多少次按钮都不会再有新的打印信息出现,因为目前FirstActivity已经处于返回栈的栈顶,每当想要再启动一个FirstActivity时,都会直接使用栈顶的Activity,因此FirstActivity只会有一个实例,仅按一次Back可以退出程序。不过当FirstActivity并未处于栈顶位置时,再启动FirstActivity还是会创建新的实例的。

下面我们来实验一下,修改FirstActivity中onCreate()方法的代码

这次我们点击按钮后启动的是SecondActivity。然后修改SecondActivity中onCreate()方法的代码

我们在SecondActivity中添加了一行打印日志,并且在按钮点击事件里加入了启动FirstActivity的代码。现在重新运行程序,在FirstActivity界面点击按钮进入SecondActivity,然后在SecondActivity界面点击按钮,又会重新进入FirstActivity。

可以看到系统创建了两个不同的FirstActivity实例,这是由于在SecondActivity中再次启动FirstActivity时,栈顶Activity已经变成了SecondActivity,因此会创建一个新的FirstActivity实例。现在按下Back键会返回到SecondActivity,再次按下Back键又会回到FirstActivity,再按一次Back键才会退出程序。

singleTask

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

我们还是通过代码来更加直观地理解一下。修改AndroidManifest.xml中FirstActivity的启动模式:

Java 复制代码
<activity android:name=".FirstActivity"
            android:launchMode="singleTask"
            android:label="This is FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
</activity>

然后在FirstActivity中添加onRestart()方法,并打印日志:Log.d("FirstActivity", "onRestart")

最后在SecondActivity中添加onDestroy()方法,并打印日志:Log.d("SecondActivity", "onDestroy")

其实从打印信息中就可以明显看出,在SecondActivity中启动FirstActivity时,会发现返回栈中已经存在一个FirstActivity的实例,并且是在SecondActivity的下面,于是SecondActivity会从返回栈中出栈,而FirstActivity重新成为了栈顶Activity,因此FirstActivity的onRestart()方法和SecondActivity的onDestroy()方法会得到执行。现在返回栈中只剩下一个FirstActivity的实例了,按一下Back键就可以退出程序。

singleInstance

singleInstance模式应该算是4种启动模式中最特殊也最复杂的一个了,你也需要多花点工夫来理解这个模式。不同于以上3种启动模式,指定为singleInstance模式的Activity会启用一个新的返回栈来管理这个Activity(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈)。

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

SecondActivity的Task id不同于FirstActivity和ThirdActivity,这说明SecondActivity确实是存放在一个单独的返回栈里的,而且这个栈中只有SecondActivity这一个Activity。

然后我们按下Back键进行返回,你会发现ThirdActivity竟然直接返回到了FirstActivity,再按下Back键又会返回到SecondActivity,再按下Back键才会退出程序,这是为什么呢?

其实原理很简单,由于FirstActivity和ThirdActivity是存放在同一个返回栈里的,当在ThirdActivity的界面按下Back键时,ThirdActivity会从返回栈中出栈,那么FirstActivity就成为了栈顶Activity显示在界面上,因此也就出现了从ThirdActivity直接返回到FirstActivity的情况。然后在FirstActivity界面再次按下Back键,这时当前的返回栈已经空了,于是就显示了另一个返回栈的栈顶Activity,即SecondActivity。最后再次按下Back键,这时所有返回栈都已经空了,也就自然退出了程序。

Activity的最佳实践

知晓当前是在哪一个Activity

这个技巧将教会你如何根据程序当前的界面就能判断出这是哪一个Activity。可能你会觉得挺纳闷的,我自己写的代码怎么会不知道这是哪一个Activity呢?然而现实情况是,在你进入一家公司之后,更有可能的是接手一份别人写的代码,因为你刚进公司就正好有一个新项目启动的概率并不高。阅读别人的代码时有一个很头疼的问题,就是当你需要在某个界面上修改一些非常简单的东西时,却半天找不到这个界面对应的Activity是哪一个。学会了本节的技巧之后,这对你来说就再也不是难题了。

我们还是在ActivityTest项目的基础上修改,首先需要新建一个BaseActivity类。右击com.example.activitytest包→New→Kotlin File/Class,在弹出的窗口中输入BaseActivity,创建类型选择Class

注意,这里的BaseActivity和普通Activity的创建方式并不一样,因为我们不需要让BaseActivity在AndroidManifest.xml中注册,所以选择创建一个普通的类就可以了。然后让BaseActivity继承自AppCompatActivity,并重写onCreate()方法

Java 复制代码
public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", this.getClass().getSimpleName());
    }
}

我们在onCreate()方法中加了一行日志,用于打印当前实例的类名。我们先是获取了当前实例的Class对象,然后再调用simpleName获取当前实例的类名。

接下来我们需要让BaseActivity成为ActivityTest项目中所有Activity的父类

修改FirstActivity、SecondActivity和ThirdActivity的继承结构,让它们不再继承自AppCompatActivity,而是继承自BaseActivity。而由于BaseActivity又是继承自AppCompatActivity的,所以项目中所有Activity的现有功能并不受影响,它们仍然继承了Activity中的所有特性。现在重新运行程序,然后通过点击按钮分别进入FirstActivity、SecondActivity和ThirdActivity的界面,这时观察Logcat中的打印信息

现在每当我们进入一个Activity的界面,该Activity的类名就会被打印出来,这样我们就可以时刻知晓当前界面对应的是哪一个Activity了。

随时随地退出程序

如果目前你手机的界面还停留在ThirdActivity,你会发现当前想退出程序是非常不方便的,需要连按3次Back键才行。按Home键只是把程序挂起,并没有退出程序。如果我们的程序需要注销或者退出的功能该怎么办呢?看来要有一个随时随地都能退出程序的方案才行。其实解决思路也很简单,只需要用一个专门的集合对所有的Activity进行管理就可以了。

下面我们就来实现一下。新建一个单例类ActivityCollector作为Activity的集合,代码如下所示

Java 复制代码
public class ActivityCollector {
    private static ActivityCollector instance;
    private ArrayList<Activity> activities;
    private ActivityCollector() {
        activities = new ArrayList<>();
    }
    public static synchronized ActivityCollector getInstance() {
        if (instance == null) {
            instance = new ActivityCollector();
        }
        return instance;
    }
    public void addActivity(Activity activity) {
        activities.add(activity);
    }
    public void removeActivity(Activity activity) {
        activities.remove(activity);
    }
    public void finishAll() {
        for (Activity activity : activities) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
        activities.clear();
    }
}

这里使用了单例类,是因为全局只需要一个Activity集合。

在集合中,我们通过一个ArrayList来暂存Activity,然后提供了一个addActivity()方法,用于向ArrayList中添加Activity;

提供了一个removeActivity()方法,用于从ArrayList中移除Activity;

最后提供了一个finishAll()方法,用于将ArrayList中存储的Activity全部销毁。

注意在销毁Activity之前,我们需要先调用activity.isFinishing来判断Activity是否正在销毁中,因为Activity还可能通过按下Back键等方式被销毁,如果该Activity没有正在销毁中,我们再去调用它的finish()方法来销毁它。

修改BaseActivity中的代码

Java 复制代码
public class ActivityCollector {
    private static ActivityCollector instance;
    private List<Activity> activities = new ArrayList<>();
    private ActivityCollector() {}
    public static synchronized ActivityCollector getInstance() {
        if (instance == null) {
            instance = new ActivityCollector();
        }
        return instance;
    }
    public void addActivity(Activity activity) {
        activities.add(activity);
    }
    public void removeActivity(Activity activity) {
        activities.remove(activity);
    }
    public void finishAll() {
        for (Activity activity : activities) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
        activities.clear();
    }
}

在BaseActivity的onCreate()方法中调用了ActivityCollector的addActivity()方法,表明将当前正在创建的Activity添加到集合里。

然后在BaseActivity中重写onDestroy()方法,并调用了ActivityCollector的removeActivity()方法,表明从集合里移除一个马上要销毁的Activity。

从此以后,不管你想在什么地方退出程序,只需要调用ActivityCollector.finishAll()方法就可以

android.os.Process.killProcess(android.os.Process.myPid())

killProcess()方法用于杀掉一个进程,它接收一个进程id参数,我们可以通过myPid()方法来获得当前程序的进程id。需要注意的是,killProcess()方法只能用于杀掉当前程序的进程,不能用于杀掉其他程序。

启动Activity的最佳写法

启动Activity的方法相信你已经非常熟悉了,

首先通过Intent构建出当前的"意图",

然后调用startActivity()或startActivityForResult()方法将Activity启动起来,

如果有数据需要在Activity之间传递,也可以借助Intent来完成。

在真正的项目开发中经常会出现对接的问题。比如SecondActivity并不是由你开发的,但现在你负责开发的部分需要启动SecondActivity,而你却不清楚启动SecondActivity需要传递哪些数据。

这时无非就有两个办法:一个是你自己去阅读SecondActivity中的代码,另一个是询问负责编写SecondActivity的同事。你会不会觉得很麻烦呢?其实只需要换一种写法,就可以轻松解决上面的窘境。修

改SecondActivity中的代码,如下所示:

Java 复制代码
public class SecondActivity extends BaseActivity {
    public static void actionStart(Context context, String data1, String data2) {
        Intent intent = new Intent(context, SecondActivity.class);
        intent.putExtra("param1", data1); // 修复了方括号为正常字符
        intent.putExtra("param2", data2);
        context.startActivity(intent);
    }
}

我们在SecondActivity 中添加了一个actionstart()方法,在这个方法中完成了Intent的构建,

另外所有SecondActivity中需要的数据都是通过actionStart()方法的参数传递过来的,然后把它们存储到Intent中,最后调用 startActivity()方法启动 SecondActivity。

现在只需要一行代码就可以启动SecondActivity

button1.setOnClickListener { SecondActivity.actionStart(this, "data1", "data2") }
        context.startActivity(intent);
    }
}

我们在SecondActivity 中添加了一个actionstart()方法,在这个方法中完成了Intent的构建,

另外所有SecondActivity中需要的数据都是通过actionStart()方法的参数传递过来的,然后把它们存储到Intent中,最后调用 startActivity()方法启动 SecondActivity。

现在只需要一行代码就可以启动SecondActivity

button1.setOnClickListener { SecondActivity.actionStart(this, "data1", "data2") }
相关推荐
众拾达人2 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
吃着火锅x唱着歌3 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
_Shirley4 小时前
鸿蒙设置app更新跳转华为市场
android·华为·kotlin·harmonyos·鸿蒙
hedalei6 小时前
RK3576 Android14编译OTA包提示java.lang.UnsupportedClassVersionError问题
android·android14·rk3576
锋风Fengfeng6 小时前
安卓多渠道apk配置不同签名
android
枫_feng7 小时前
AOSP开发环境配置
android·安卓
叶羽西7 小时前
Android Studio打开一个外部的Android app程序
android·ide·android studio
qq_171538859 小时前
利用Spring Cloud Gateway Predicate优化微服务路由策略
android·javascript·微服务
Vincent(朱志强)10 小时前
设计模式详解(十二):单例模式——Singleton
android·单例模式·设计模式
mmsx10 小时前
android 登录界面编写
android·登录界面