编者按:本文节选自郭霖著《第一行代码——Android》一书中的部分章节。
活动的生命周期
掌握活动的生命周期对任何 Android 开发者来说都非常重要,当你深入理解活动的生命周期之后,就可以写出更加连贯流畅的程序,并在如何合理管理应用资源方面发挥得游刃有余。你的应用程序将会拥有更好的用户体验。
活动的生命周期:返回栈
经过前面几节的学习,我相信你已经发现了这一点,Android 中的活动是可以层叠的。我们每启动一个新的活动,就会覆盖在原活动之上,然后点击 Back 键会销毁最上面的活动,下面的一个活动就会重新显示出来。
其实 Android 是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack)。栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们按下 Back 键或调用finish()
方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。
示意图 1 展示了返回栈是如何管理活动入栈出栈操作的。
图 1 返回栈工作示意图
活动的生命周期:活动状态
每个活动在其生命周期中最多可能会有 4 种状态。
运行状态
当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验。
暂停状态
当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。你可能会觉得既然活动已经不在栈顶了,还怎么会可见呢?这是因为并不是每一个活动都会占满整个屏幕的,比如对话框形式的活动只会占用屏幕中间的部分区域,你很快就会在后面看到这种活动。处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只有在内存极低的情况下,系统才会去考虑回收这种活动。
停止状态
当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。
销毁状态
当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活动,从而保证手机的内存充足。
活动的生命周期:活动的生存期
Activity 类中定义了 7 个回调方法,覆盖了活动生命周期的每一个环节,下面就来一一介绍这 7 个方法。
onCreate()
。这个方法你已经看到过很多次了,每个活动中我们都重写了这个方法,它会在活动第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如说加载布局、绑定事件等。onStart()
。这个方法在活动由不可见变为可见的时候调用。onResume()
。这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。onPause()
。这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗 CPU 的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。onStop()
。这个方法在活动完全不可见的时候调用。它和onPause()
方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()
方法会得到执行,而onStop()
方法并不会执行。onDestroy()
。这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。onRestart()
。这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。
以上 7 个方法中除了onRestart()
方法,其他都是两两相对的,从而又可以将活动分为 3 种生存期。
完整生存期。活动在
onCreate()
方法和onDestroy()
方法之间所经历的,就是完整生存期。一般情况下,一个活动会在onCreate()
方法中完成各种初始化操作,而在onDestroy()
方法中完成释放内存的操作。可见生存期。活动在
onStart()
方法和onStop()
方法之间所经历的,就是可见生存期。在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两个方法,合理地管理那些对用户可见的资源。比如在onStart()
方法中对资源进行加载,而在onStop()
方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。前台生存期。活动在
onResume()
方法和onPause()
方法之间所经历的就是前台生存期。在前台生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行交互的,我们平时看到和接触最多的也就是这个状态下的活动。
为了帮助你能够更好地理解,Android 官方提供了一张活动生命周期的示意图,如图 2 所示。
图 2 活动的生命周期
活动的生命周期:体验活动的生命周期
讲了这么多理论知识,也是时候该实战一下了,下面我们将通过一个实例,让你可以更加直观地体验活动的生命周期。
这次我们不准备在 ActivityTest 这个项目的基础上修改了,而是新建一个项目。因此,首先关闭 ActivityTest 项目,点击导航栏 File→Close Project。然后再新建一个 ActivityLifeCycleTest 项目,新建项目的过程你应该已经非常清楚了,不需要我再进行赘述,这次我们允许 Android Studio 帮我们自动创建活动和布局,这样可以省去不少工作,创建的活动名和布局名都使用默认值。
这样主活动就创建完成了,我们还需要分别再创建两个子活动——NormalActivity 和 DialogActivity,下面一步步来实现。
右击 com.example.activitylifecycletest 包→New→Activity→Empty Activity,新建 NormalActivity,布局起名为 normal_layout。然后使用同样的方式创建 DialogActivity,布局起名为 dialog_layout。
现在编辑 normal_layout.xml 文件,将里面的代码替换成如下内容:
这个布局中我们就非常简单地使用了一个 TextView,用于显示一行文字,在下一章中你将会学到更多关于 TextView 的用法。
然后再编辑 dialog_layout.xml 文件,将里面的代码替换成如下内容:
两个布局文件的代码几乎没有区别,只是显示的文字不同而已。
NormalActivity 和 DialogActivity 中的代码我们保持默认就好,不需要改动。
其实从名字上你就可以看出,这两个活动一个是普通的活动,一个是对话框式的活动。可是我们并没有修改活动的任何代码,两个活动的代码应该几乎是一模一样的,在哪里有体现出将活动设成对话框式的呢?别着急,下面我们马上开始设置。修改 AndroidManifest.xml 的<activity>
标签的配置,如下所示:
这里是两个活动的注册代码,但是 DialogActivity 的代码有些不同,我们给它使用了一个android:theme
属性,这是用于给当前活动指定主题的,Android 系统内置有很多主题可以选择,当然我们也可以定制自己的主题,而这里@style/Theme.AppCompat.Dialog
则毫无疑问是让 DialogActivity 使用对话框式的主题。
接下来我们修改 activity_main.xml,重新定制主活动的布局,将里面的代码替换成如下内容:
可以看到,我们在 LinearLayout 中加入了两个按钮,一个用于启动 NormalActivity,一个用于启动 DialogActivity。
最后修改 MainActivity 中的代码,如下所示:
在onCreate()
方法中,我们分别为两个按钮注册了点击事件,点击第一个按钮会启动 NormalActivity,点击第二个按钮会启动 DialogActivity。然后在 Activity 的 7 个回调方法中分别打印了一句话,这样就可以通过观察日志的方式来更直观地理解活动的生命周期。
现在运行程序,效果如图 3 所示。
图 3 MainActivity 界面
这时观察 logcat 中的打印日志,如图 4 所示。
图 4 启动程序时的打印日志
可以看到,当 MainActivity 第一次被创建时会依次执行onCreate()
、onStart()
和onResume()
方法。然后点击第一个按钮,启动 NormalActivity,如图 5 所示。
图 5 NormalActivity 界面
此时的打印信息如图 6 所示。
图 6 打开 NormalActivity 时的打印日志
由于 NormalActivity 已经把 MainActivity 完全遮挡住,因此onPause()
和onStop()
方法都会得到执行。然后按下 Back 键返回 MainActivity,打印信息如图 7 所示。
图 7 返回 MainActivity 的打印日志
由于之前 MainActivity 已经进入了停止状态,所以onRestart()
方法会得到执行,之后又会依次执行onStart()
和onResume()
方法。注意此时onCreate()
方法不会执行,因为 MainActivity 并没有重新创建。
然后再点击第二个按钮,启动 DialogActivity,如图 8 所示。
图 8 DialogActivity 界面
此时观察打印信息,如图 9 所示。
图 9 打开 DialogActivity 时的打印日志
可以看到,只有onPause()
方法得到了执行,onStop()
方法并没有执行,这是因为DialogActivity
并没有完全遮挡住MainActivity
,此时MainActivity
只是进入了暂停状态,并没有进入停止状态。相应地,按下 Back 键返回 MainActivity 也应该只有onResume()
方法会得到执行,如图 10 所示。
图 10 再次返回 MainActivity 的打印日志
最后在 MainActivity 按下 Back 键退出程序,打印信息如图 11 所示。
图 11 退出程序时的打印日志
依次会执行onPause()
、onStop()
和onDestroy()
方法,最终销毁 MainActivity。
这样活动完整的生命周期你已经体验了一遍,是不是理解得更加深刻了?
活动的生命周期:活动被回收了怎么办
前面我们已经说过,当一个活动进入到了停止状态,是有可能被系统回收的。那么想象以下场景:应用中有一个活动 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 中添加如下代码就可以将临时数据进行保存:
数据是已经保存下来了,那么我们应该在哪里进行恢复呢?细心的你也许早就发现,我们一直使用的onCreate()
方法其实也有一个Bundle
类型的参数。这个参数在一般情况下都是null
,但是如果在活动被系统回收之前有通过onSaveInstanceState()
方法来保存数据的话,这个参数就会带有之前所保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可。
修改 MainActivity 的onCreate()
方法,如下所示:
取出值之后再做相应的恢复操作就可以了,比如说将文本内容重新赋值到文本输入框上,这里我们只是简单地打印一下。
不知道你有没有察觉,使用Bundle
来保存和取出数据是不是有些似曾相识呢?没错!我们在使用 Intent 传递数据时也是用的类似的方法。这里跟你提醒一点,Intent 还可以结合Bundle
一起用于传递数据,首先可以把需要传递的数据都保存在Bundle
对象中,然后再将Bundle
对象存放在 Intent 里。到了目标活动之后先从 Intent 中取出Bundle
,再从Bundle
中一一取出数据。具体的代码我就不写了,要学会举一反三哦。
图书简介:https://www.ituring.com.cn/book/1841
相关阅读:
第一行代码——Android(一):前行必备,如何使用日志工具
评论