关于 Activity
的生命周期,可以说是每一位 Android 开发者的入门必备知识,也可以说是入了门就几乎不会再翻看的内容。
相信同行们都知道,这是初级开发者面试必问的问题,虽然问这个问题的意义并不大,主要还是面试官要找一个开场过渡一下罢了。
虽说简单,但是如果答不上来,那可不是出师未捷身先死?
先来放一张来自官方文档陈年经典老图:
![Activity Lifecycle](https://LiarrDev.github.io/post-images/1563970868282.png)
下面一一介绍一下生命周期的七个函数调用:
onCreate()
:当Activity
第一次创建时会被调用。这是生命周期的第一个方法,在这个方法中,可以做一些初始化工作,比如调用setContentView()
去加载界面布局资源,初始化Activity
所需的数据。当然也可借助onCreate()
方法中的Bundle
对象来回复异常情况下Activity
结束时的状态。onRestart()
:表示Activity
正在重新启动。一般情况下,当当前Activity
从不可见重新变为可见状态时,onRestart()
就会被调用。这种情形一般是用户行为导致的,比如用户按 HOME 键切换到桌面或打开了另一个新的Activity
,接着用户又回到了这个Actvity
。onStart()
:表示Activity
正在被启动,即将开始,这时Activity
已经出现了,但是还没有出现在前台,无法与用户交互。这个时候可以理解为Activity
已经显示出来,但是我们还看不到。onResume()
:表示Activity
已经可见了,且出现在前台并开始活动。需要和onStart()
对比,onStart()
的时候Activity
还在后台,onResume()
的时候Activity
才显示到前台。onPause()
:表示Activity
正在停止但仍可见,正常情况下,紧接着onStop()
就会被调用。在特殊情况下,如果这个时候快速地回到当前Activity
,那么onResume()
就会被调用(极端情况)。onPause()
中不能进行耗时操作,会影响到新Activity
的显示。因为onPause()
必须执行完,新的Activity
的onResume()
才会执行。onStop()
:表示Activity
即将停止,不可见,位于后台。可以做稍微重量级的回收工作,同样不能太耗时。onDestory()
:表示Activity
即将销毁,这是Activity
生命周期的最后一个回调,可以做一些回收工作和最终的资源回收。
另一张来自《Android programming: the big nerd ranch guide》的图我觉得也能够很好地帮助理解:
![Activity State](https://LiarrDev.github.io/post-images/1563971365278.jpg)
这里说的是 Activity
的三种运行状态:
- Resumed(活动状态):又叫 Running 状态,这个
Activity
正在屏幕上显示,并且有用户焦点。这个很好理解,就是用户正在操作的那个界面。 - Paused(暂停状态):这是一个比较不常见的状态。这个
Activity
在屏幕上是可见的,但是并不是在屏幕最前端的那个Activity
。比如有另一个非全屏的或者透明的Activity
是 Resumed 状态,没有完全遮盖这个Activity
。 - Stopped(停止状态):当
Activity
完全不可见时,此时Activity
还在后台运行,仍然在内存中保留Activity
的状态,并不是完全销毁。这个也很好理解,当跳转的另外一个界面,之前的界面还在后台,按回退按钮还会恢复原来的状态,大部分软件在打开的时候,直接按 HOME 键,并不会关闭它,此时的Activity
就是 Stopped 状态。
当然,有些文章也会把它描述成五种状态,并不影响理解:
![Activity 的五种状态](https://LiarrDev.github.io/post-images/1564065726563.png)
只不过是把启动和销毁两种状态加进来罢了,实际上讨论这两种状态的意义并不大,可以作为参考。
综合之后,我们可以参考这张图:
![Activity State and Lifecycle](https://LiarrDev.github.io/post-images/1565595515187.png)
接下来是关于生命周期在一些情况下的表现。
- 正常情况下从一个
Activity
启动另一个Activity
,两个Activity
的生命周期如何交替?
可能有些人不太理解为什么要先执行原 Activity
的 onPause()
方法再启动新 Activity
的创建,可以假设这么一种情况,比如你通过一个按钮来启动一个 Activity
,快速双击这个按钮,如果我们等新的 Activity
创建完成了再执行原 Activity
的 onPause()
,那么就可能会创建两个相同的 Activity
在栈中,所以必须要先执行原 Activity
的 onPause()
,使其进入 Paused 状态,让它无法与用户交互。
- 点击 OVERVIEW 键调出最近任务后会执行哪些生命周期函数?
很多人不知道 OVERVIEW 键是哪个,因为这种说法也很少有,其实就是 Android 手机三大金刚键除了 HOME 和 BACK 剩下的那个,默认情况下该键用于调出最近任务:
![Overview](https://LiarrDev.github.io/post-images/1563976290962.png)
但实际上该键经历了很多变更,比如在早期该键其实用于呼出菜单,直至近年来才统一为默认调出最近任务,但一些老用户(比如我)还是会将其修改为呼出菜单,而将长按 HOME 键设置为调出最近任务。
所以这里的意思是调出上述最近任务界面后,原来的 Activity
会执行哪些生命周期函数:
一般会执行 onPause()
和 onStop()
两个方法,但不绝对,也有部分机型并不会执行生命周期的任何方法。
- 锁屏后当前
Activity
会执行哪些生命周期函数?
和用户点击 HOME 键回到桌面或者调出最近任务相似,锁屏后也会执行 onPause()
以及 onStop()
两个方法。
- 什么时候会仅执行
onPause()
而不执行onStop()
?
上面介绍 Activity
运行状态时也有提到,执行 onPause()
而不执行 onStop()
,即 Activity
进入了 Paused 状态,此时比如有另一个非全屏的或者透明的 Activity
是 Resumed 状态,即在前台和用户交互,原来这个可见但不可交互的 Activity
就是处于 Paused 状态,不会执行到 onStop()
。
![Dialog 主题的 Activity](https://LiarrDev.github.io/post-images/1563977782236.png)
如『Android 使用 Dialog 样式的 Activity』一文提到的把 Activity
的主题设置为 Dialog 样式。
- 弹出
AlertDialog
后Activity
会执行哪些生命周期函数?
![弹出 AlertDialog](https://LiarrDev.github.io/post-images/1563978862016.png)
并不会执行 Activity
的任何生命周期函数。
其实这里有个很大的误区,许多刚入门的开发者或许会在一些博客中看到一些博主笼统地概括:弹出弹窗后 Activity
就会进入 Paused 状态。
事实上并不是,而只有上一点提到的 Dialog 主题的 Activity
才会使原 Activity
进入 Paused 状态,而普通的 AlertDialog
并不会使原 Activity
进入 Paused 状态。
- 长按 POWER 键会执行哪些生命周期函数?
长按 POWER 键一般会弹出一个关机或重启的选项界面,不同的 ROM 其实界面也不尽相同,比如原生 Android 9.0 中是从侧边弹出一个选项菜单,而国内的大部分 ROM 会弹出一个全屏的界面让用户选择。
||
|--|--|
那么这两种情况是否会触发不一样的生命周期函数呢?
答案是否定的,长按 POWER 键并不会执行任何生命周期函数,无论你是半遮挡还是全遮挡。
- 下拉状态栏会执行哪些生命周期函数?
下拉通知栏不会执行任何生命周期函数。
- 切换屏幕方向时会执行哪些生命周期函数?
在横竖屏切换的过程中,会发生 Activity
被销毁并重建的过程。
虽然可以简单粗暴地描述成 Activity
经历了一次完整的生命周期后再次重建,但实际上要复杂些。
了解这种情况下的生命周期时,首先应该了解这两个回调:onSaveInstanceState()
和 onRestoreInstanceState()
。
在 Activity
由于异常情况下终止时,系统会调用 onSaveInstanceState()
来保存当前 Activity
的状态。这个方法的调用是在 onDestroy()
之前,它和onPause()
没有既定的时序关系,该方法只在 Activity
被异常终止的情况下调用。
当异常终止的 Activity
被重建以后,系统会调用 onRestoreInstanceState()
,并且把 Activity
销毁时 onSaveInstanceState()
方法所保存的 Bundle
对象参数同时传递给 onRestoreInstanceState()
和 onCreate()
方法,你可以很明显的看到,onCreate()
方法入参就有一个 Bundle
对象。
因此,可以通过 onRestoreInstanceState()
方法来恢复 Activity
的状态,该方法的调用时机是在 onStart()
之后。
其中 onCreate()
和 onRestoreInstanceState()
方法来恢复 Activity
的状态的区别:onRestoreInstanceState()
回调则表明其中 Bundle
对象非空,不用加非空判断,而 onCreate()
需要非空判断。建议使用 onRestoreInstanceState()
。
![异常终止的 Activity](https://LiarrDev.github.io/post-images/1563979849165.png)
切换屏幕方向时 Activity
生命周期如下:
尽管官方文档中提到 onSaveInstanceState()
会调用在 onStop()
之前,并可能会调用在 onPause()
之前,但是我实际测试中发现,它在 Android 9.0 上也有可能会在 onStop()
之后才调用,我暂时找不到解释的理由,所以对于官方的说法,我暂时做一些修改,onSaveInstanceState()
会在 onDestroy()
之前调用,跟 onPause()
和 onStop()
没有既定的时序关系。
当然官方的说法理论上是比我更有说服力的,所以我希望有经验的开发者们能替我解释一下这个现象,或者我以后搞明白了再来修改,先放一张图:
![切换屏幕方向时打印的生命周期日志](https://LiarrDev.github.io/post-images/1564062456275.gif)
可以看到,onSaveInstanceState()
方法打印的日志在 onStop()
之后,所以才会出现了上面的疑问。
跟《第一行代码——Android》的作者郭霖交流过后得到的回复是,onSaveInstanceState()
方法不在 Activity
正常的生命周期之中,所以不同系统版本的表现都有可能不一样,只要知道其作用即可,不要依赖于执行的先后顺序去处理逻辑。
在开发中,常常会禁用屏幕旋转,或者在 Activity
中指定以下属性来避免切换屏幕方向时 Activity
的销毁与重建:
<activity
...
android:configChanges="orientation|screenSize" />
指定该属性后,Activity
则会回调以下的方法:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
- 分屏模式下会执行哪些生命周期函数?
从 Android N 开始就已经引入了 Split Screen,我们通常叫分屏模式或者多窗口模式,打开最近任务就可以选取任意两个应用进入 Split Screen,通常情况下最先选中的应用会处在上方,后选中处在下方,当然,也有一些 ROM 会采用拖动的策略,且操作焦点一般会在后选中的应用中。
讨论 Split Screen 下的生命周期调用,要分成三种情况,分别是进入 Split Screen 时、处于 Split Screen 时、退出 Split Screen 时。
进入 Split Screen 时,即选择应用进入分屏模式,该情况又分为两种小情况,即先选中的应用和后选中的应用。
先来看进入 Split Screen 时先选中的应用,这时该应用会处于屏幕上半部分,且未获得操作焦点:
![作为首个选中的应用进入 Split Screen](https://LiarrDev.github.io/post-images/1565538197903.gif)
进入 Split Screen 时 Activity
同样会执行销毁及重建的过程,所以生命周期如下:
这些函数的执行是连贯的,你可能会认为当用户选中了第二个应用时才会执行 onPause()
,实际上并不是,因为用户选中第一个应用后马上就失去操作焦点,此时操作焦点处于让用户选择第二个应用的 OVERVIEW 中,所以当用户选中第一个应用时该应用就处于 Paused 状态。
再来看进入 Split Screen 时后选中的应用,这时该应用会处于屏幕下半部分,且获得了操作焦点:
![作为次个选中的应用进入 Split Screen](https://LiarrDev.github.io/post-images/1565592336195.gif)
进入 Split Screen 时 Activity
同样会执行销毁及重建的过程,所以生命周期如下:
由于此时该应用的 Activity
获得了操作焦点,所以处于 Resumed 状态。
处于 Split Screen 时的生命周期十分好理解,就是获得焦点与失去焦点的切换过程,但由于此时 Activity
总是可见的,所以,当获得焦点时,Activity
就处于 Resumed 状态,失去焦点时,Activity
就处于 Paused 状态,所以在两个应用中切换焦点,就会分别执行 onPause()
和 onResume()
。
若在处于 Split Screen 时我们进行一些其他的操作,如点击 HOME 键或 OVERVIEW 键,那么处于屏幕上方的应用则会向上折叠,将屏幕下方的空间扩大,适应用户的操作。
![Split Screen 时点击 HOME 键](https://LiarrDev.github.io/post-images/1565603542753.gif)
处于上方的应用假如处于 Resumed 状态,那么此时就会调用 onPause()
,如果处于 Paused 状态,则不会调用任何生命周期函数,因为其依然可见,只是失去了操作焦点。
但是你点击上方该应用的区域,也是无法回到 Resumed 状态的,因为 Split Screen 必须要由两个应用构成,点按 HOME 键后系统会认为你准备选取第二个应用来填充下方区域,所以此时你要么退出 Split Screen,要么选择应用,不可以单独让一个应用处于 Split Screen。
![Split Screen 时尝试调整单个应用](https://LiarrDev.github.io/post-images/1565604997571.gif)
而处于下方的应用则如平常退到后台一样,如果处于 Paused 状态,则直接执行 onStop()
,如果处于 Resumed 状态,则还需执行 onPause()
。
退出 Split Screen 时也分为两种小情况,即退出 Split Screen 后该应用是否处于界面上。
当退出 Split Screen 时选择该应用在界面上活动,则和平时我们正常使用无差别:
![退出 Split Screen 且在前台](https://LiarrDev.github.io/post-images/1565593206792.gif)
与进入 Split Screen 时相似,此时的 Activity
也同样进行了一次销毁重建:
这个生命周期中有两个判断,是因为同时也有几种情况,我集中一起解说,第一个条件是判断想要处于前台的 Activity
在 Split Screen 时是否处于 Resumed 状态,即是否获取到用户的操作焦点,比较好理解,如果处于 Resumed 状态则需要先执行 onPause()
,否则就不需要再执行;接下来就是一个销毁重建的过程,但是测试中意外发现也会出现两种情况,即一开始处于 Paused 状态的应用 Activity
在重建过后会执行两次 onStart()
和 onResume()
,而一开始处于 Resumed 状态的应用 Activity
则不会,这就是第二个条件判断,对于此现象我暂时未找到解释的说法。
当退出 Split Screen 时选择其他应用在界面上活动,此时我们的应用则处于后台:
![退出 Split Screen 处在后台](https://LiarrDev.github.io/post-images/1565596076296.gif)
处在后台的应用 Activity
生命周期如下:
跟我们正常情况下切换到后台也很相似,但还是要作判断,如果 Activity
处于 Resumed 状态则需先执行 onPause()
再执行 onStop()
。
但是,这样就结束了么?并不是。
当你把该应用从后台再切回前台的时候就会发现,该 Activity
也同样执行了销毁重建:
与上面处于前台的 Activity
不同,这里不会根据条件判断是否需要执行两次 onStart()
和 onResume()
。
- 强制退出当前应用会执行哪些生命周期函数?
虽然原生 Android 似乎没有提供强制退出当前应用的方法,但很多国内的 ROM 都定制了该功能,如 MIUI 可以设定长按 BACK 键强制关闭等。
![MIUI 长按 BACK 键强制退出](https://LiarrDev.github.io/post-images/1564145133442.png)
Android 9.0 的测试中发现强制退出当前应用时调用了 onPause()
,而后面的函数没有调用,这个问题属于边界问题,实际上 onPause()
也不一定会调用,因为强制退出应用实际上是直接从系统中 Kill 掉应用所在的进程,进程不在了,Activity
也就无法监听到生命周期的变化。
- 从最近任务中结束应用会执行哪些生命周期函数?
前面提到呼出最近任务会调用 onPause()
和 onStop()
,那么在最近任务中结束应用呢?
在 Android 9.0 的测试中见到,会调用 onDestroy()
。
- 关机时当前应用会执行哪些生命周期函数?
上面提到,长按 POWER 键是不会触发生命周期函数的,那么长按 POWER 键再选择关机呢?
测试中显示会执行 onPause()
、onStop()
和 onSaveInstanceState()
,可以发现没有执行 onDestroy()
,这个问题比较偏门,属于边界问题,个人认为也可能会产生多种情况。但基本情况下是如此的,它和我们强制退出当前应用比较相似,关机的时候从系统层将应用杀死,所以不会执行 onDestroy()
。
- 当系统内存不足时,哪些
Activity
可能会被杀死?
当系统内存不足时,优先级低的 Activity
会被杀死,而 Activity
优先级的划分和 Activity
的三种运行状态也是对应的。
- Resumed 状态的
Activity
优先级最高,它正在和用户交互。 - Paused 状态的
Activity
优先级稍低,它虽然不能和用户交互,但它依然可见。 - Stopped 状态的
Activity
优先级最低,它已完全不可见。
当系统内存不足时,会按照上述优先级从低到高去杀死目标 Activity
所在的进程。我们在平常使用手机时,能经常感受到这一现象。这种情况下数组存储和恢复过程和上述情况一致,生命周期情况也一样。
这里仅讨论 Activity
所在进程的优先级,其实该问题可以放大到应用进程中进行讨论,比如和 Service
的优先级比较等。
简单做一个总结,我是这样理解:凡是涉及到当前 Activity
的变动,才会触发生命周期的函数,而涉及到系统级的事件不会导致 Activity
的生命周期变化,需要注意的是,虽然弹出 AlertDialog
依赖于 Activity
,但实际上也有系统级的 AlertDialog
,所以我把 AlertDialog
归类于系统级事件。
该文大致回顾了 Android 中 Activity
的生命周期,它是每个 Android 开发者的基础知识,一定要掌握,掌握的目的,当然不是为了应付面试,而是为了在开发中更好的运用。
毕竟根基不牢,再高的建筑也会倒塌。
另外,我基于 Activity
的生命周期,开发了一款屏幕监视器——『Screen Watchdog』,可以让你知道是否有人偷看了你的手机,有需要的话可以自取哟。