基本概念
Activity 用来做界面显示,交互,也是程序的入口。
生命周期
生命周期流转图

注意: 在实现生命周期方法时必须始终先调用超类实现,然后再执行其他操作。
1 |
|
生命周期回调方法汇总表
- 生命周期回调方法汇总表

onPause和onStop的区别onPause:失去焦点,不可交互onStop:完全被遮挡,Activity不可见
示例:Activity 在弹出 Dialog 时,只是失去焦点,并没有完全不可见,所以只会执行 onPause ;而 Activity 在启动另一个 Activity 时被完全遮挡,就会执行到 onStop
A 启动 B 生命周期的执行顺序
1 | // 启动A |
可以清楚的看到在 A 启动 B 的流程中,onPause 在失去焦点后就立即执行,当 B 完全启动后并遮挡了 A,onStop 才开始执行。同理,B 在退出过程中,等 A 完全恢复并遮挡 B 后,B 才开始执行 onStop -> onDestroy。onStop -> onRestart 这个流程出现在:Activity 被遮挡但没有销毁,下次恢复时就会执行 onRestart -> onStart。
finish() 对生命周期的影响
- 在
onCreate中调用,执行顺序onCreate -> onDestroy - 在
onStart中调用,执行顺序onCreate -> onStart -> onStop -> onDestroy
Activity 重建的影响因素
如下因素的变化,都会导致 Activity 重建,也就是会走 onDestroy -> onCreate:
- mcc
国际移动用户识别码所属国家代号是改变了,sim被侦测到了,去更新mcc。mcc是移动用户所属国家代号 - mnc
MNC是移动网号码,最多由两位数字组成,用于识别移动用户所归属的移动通信网 - locale
地址改变了,用户选择了一个新的语言会显示出来 - touchscreen
触摸屏是改变了,通常是不会发生的 - keyboard
键盘发生了改变,例如用户用了外部的键盘 - keyboardHidden
键盘的可用性发生了改变 - navigation
导航发生了变化,通常也不会发生 - screenLayout
屏幕的显示发生了变化,不同的显示被激活 - fontScale
字体比例发生了变化,选择了不同的全局字体 - uiMode
用户的模式发生了变化 - orientation
屏幕方向改变了 - screenSize
屏幕大小改变了 - smallestScreenSize
屏幕的物理大小改变了,如:连接到一个外部的屏幕上
这些影响因素可以在 AndroidManifest.xml 中设置来屏蔽掉,凡是在这申明后,将不会影响生命周期。
1 | <activity android:name=".fourcomponents.ActivityLifeCycleA" |
屏幕旋转对生命周期的影响
- 常规流程
Activity销毁重建
屏幕旋转会完整的触发这两个生命周期,其中状态恢复在onCreate和onRestoreInstanceState中都可以实现
1 | TAG:: onCreate |
- 避免
Activity销毁重建
在屏幕旋转时,避免Activity销毁重建,可以在AndroidManifest.xml中设置对应属性(Android 3.2及以上需要设置screenSize):
1 | <activity android:name=".fourcomponents.ActivityLifeCycleA" |
设置后屏幕旋转将完全不影响 Activity 的生命周期,如果想监听屏幕旋转,可以重写如下方法:
1 |
|
“锁屏/解锁”对生命周期的影响
“锁屏/解锁” 整个流程包含:锁屏,亮屏,解锁三个步骤:
不涉及横竖屏切换
如:当前 Activity 为竖屏,在竖屏状态下,锁屏并解锁,不做方向切换,即正常流程:
锁屏:onPause -> onStop
解锁:onRestart -> onStart -> onResume
涉及横竖屏切换
如:当前 Activity 为横屏,在横屏状态下锁屏会显示锁屏界面,涉及方向切换,当前 Activity 会被销毁重建。
锁屏:onPause -> onStop -> onDestroy -> onCreate -> onStart -> onResume -> onPause
- 解锁后当前
Activity变为竖屏
解锁:onResume - 解锁后当前
Activity仍然转换为横屏
即会出现当前Activity销毁重建过程
解锁:onResume -> onPause -> onStop -> onDestroy -> onCreate -> onStart -> onResume
横竖屏切换监听屏幕旋转
如:当前 Activity 为横屏,并且设置了属性 android:configChanges="orientation|screenSize" 。即当前 Activity 不会销毁重建,方向切换时会被监听。
锁屏:onPause -> onStop -> onConfigurationChanged,此时当前 Activity 已经被切换到竖屏。
解锁:onRestart -> onStart -> onResume -> onConfigurationChanged
小结
- 屏幕旋转
如果涉及屏幕旋转,当前Activity在“锁屏/解锁”两个阶段都会出现销毁重建(onDestroy -> onCreate),如果此时监听了屏幕旋转,则在方向切换时仅执行onConfigurationChanged。 - 锁屏
总体来说会先执行onPause -> onStop,即不可见流程。如果屏幕旋转当前Activity重建,会执行到onPause,同时解锁会直接从onResume开始;如果监听了旋转,则屏幕切换时仅会执行onConfigurationChanged。 - 亮屏
不影响生命周期 - 解锁
总体来说会先执行onRestart --> onResume,即恢复可见流程。如果当前Activity重建过,则直接从onResume恢复;如果监听了旋转,则屏幕切换时仅会执行onConfigurationChanged。
?为什么锁屏时,如果出现
Activity销毁重建时,只执行到onPause,而不执行onStop?
Home 键对生命周期的影响
按下 Home 键不影响生命周期,正常的不可见和恢复可见流程:
不可见流程:onPause -> onStop
恢复可见流程:onRestart -> onStart -> onResume
状态栏下拉对生命周期的影响
下拉状态栏不影响生命周期,但是如果需要监听状态栏下拉,可以重写如下方法:
1 |
|
保存 Activity 的状态
状态保存和恢复
涉及到两个函数:onSaveInstanceState() 和 onRestoreInstanceState() ,这两个函数大部分情况下并不会成对出现。
如果没有重写这两个函数,系统 Activity 类的 onSaveInstanceState() 默认实现也会恢复部分 Activity 状态。具体地讲,默认实现会为布局中的每个 View 调用相应的 onSaveInstanceState() 方法,让每个视图都能提供有关自身的应保存信息。Android框架中几乎每个小部件都会根据需要实现此方法,以便在重建 Activity 时自动保存和恢复对 UI 所做的任何可见更改。例如 EditText 小部件保存用户输入的任何文本,CheckBox 小部件保存复选框的选中或未选中状态。
注意:想要保存其状态的每个小部件,必须提供一个唯一的
ID(通过android:id属性)。如果小部件没有ID,则系统无法保存其状态;如果是相同的ID,数据会被覆盖,在恢复时数据显示出错。
保存的数据
数据通过 Bundle 保存起来,使用 putString() 和 putInt() 等方法以[名称-值]对形式保存有关 Activity 状态的瞬时数据,如成员变量,UI 状态等。如果需要保存持久数据,需要在 onPause 中保存。
onSaveInstanceState()触发时机
在 Activity 不可见时触发,也就是 onPause --> onSaveInstanceState --> onStop,但是系统并不是每次都会调用。
调用
遵循一个重要原则,即当系统存在“未经你许可”销毁 Activity 的可能时,onSaveInstanceState 会被系统调用,这是系统的责任:必须要提供一个机会让你保存你的数据。
- 启动另外一个
Activity并被遮挡 - 屏幕旋转时当前
Activity销毁并被重新创建 Home键相关操作- 按下电源键灭屏
不调用
- 直接退出当前
Activity - 被
kill命令杀掉
onRestoreInstanceState()触发时机
在 Activity 恢复可见时触发,也就是 onStart --> onRestoreInstanceState --> onResume,同样系统也不是每次都会调用。
调用
重要原则,即当 Activity 确实已经被销毁。
- 屏幕旋转时当前
Activity销毁并被重新创建
不调用
- 当
Activity并没有销毁而只是进入生命周期恢复过程中,并不会被调用,直接走流程onRestart --> onStart --> onResume - 被
kill命令杀掉
示例
可以参考:“屏幕旋转对生命周期的影响”中“常规流程 Activity 销毁重建”的 log 打印。
Activity存储方式:栈
基本概念
- 返回栈(Back Stack)
返回栈: 这些Activity按照各自的打开顺序排列在栈中,表示Activity的存储方式。返回栈以“后进先出”对象结构运行,并且栈中的Activity永远不会重新排列,仅推入和弹出栈。由当前Activity启动时推入堆栈;用户使用“返回”按钮退出时弹出堆栈。

源码中查看推测是如下结构来存储返回栈的,但是似乎不像是栈的操作?
1 | // TaskRecord.java中定义,暂时没有看出来是栈存储结构? |
- 任务(Task)
任务:在执行特定作业时与用户交互的一系列Activity,使用返回栈记录这些Activity的执行顺序。任务分为前台任务和后台任务,前台任务接受用户交互;任务被移动到后台时,整个返回栈顺序不变。任务可以是同一个应用程序中的Activity,也可以是跨应用的Activity,放到同一个任务中,以维护这种无缝的用户体验。

源码中通过如下方式来记录前后台任务列表,每个任务对应一个 TaskRecord:
1 | // ActivityStatck.java |
前后台任务切换时,仅仅把需要的后台任务移动到最前面,其他任务顺序不变:
1 | // ActivityStatck::moveTaskToFrontLocked方法中 |
示例
- 命令
adb shell dumpsys activity,通过该命令可以显示所有Activity的任务及存储信息,对应源码为:
1 | // 1. ActivityManagerService.java |
- 任务只包含同一个应用的
Activity
1 | Display #0 (activities from top to bottom): |
由于返回栈中的 Activity 永远不会重新排列,因此如果应用启动特定 Activity,则会创建该 Activity 的新实例并推入堆栈中(而不是将 Activity 的任一先前实例置于顶部),因此应用中的一个 Activity 可能会多次实例化。如上面 Log 显示,A 和 B 有多次实例化。

- 任务包含不同应用的
Activity
1 | // 1. ActivityManagerService::dumpActivitiesLocked打印 |
使用清单文件定义启动模式
在清单文件中声明 Activity 时,可以使用 android:launchMode 属性指定 Activity 应该如何与任务关联,默认为 android:launchMode="standard",launchMode 属性的启动模式共有四种:
standard
系统默认,Activity 会被多次实例化。
singleTop
如果当前任务的顶部已存在该 Activity 的一个实例,则系统不会创建 Activity 的新实例;如果不在顶部,则和 standard 一样,创建多个实例。假设任务的返回栈顺序是 A-B-C-D,并且 D 位于顶部,再启动 D 时,示例对比:
- 如果
D为standard模式,将会新建D的实例,返回栈为A-B-C-D-D - 如果
D为singleTop模式,不会创建D的实例,返回栈还是A-B-C-D
singleTask
系统根据 taskAffinity 来决定是否开启新任务,taskAffinity 如果不设置,默认值为包名。已有的任务中如果已经存在该 Activity 实例,下次调用时会把该任务的返回栈中位于这个实例上面所有的 Activity 全部结束掉,即最终这个 Activity 实例会位于该任务的堆栈顶端中。
taskAffinity为默认值或包名,在原任务中装载该Activity。如下示例:
1 | // 任务及返回栈信息 |
对应的生命周期流程:B:: onPause -> A:: onNewIntent -> A:: onRestart -> A:: onStart -> A:: onResume -> B:: onStop -> B:: onDestroy
taskAffinity不是包名,开启新的任务并加载该Activity到底部,后续新开启的Activity也会在这个新任务中。
1 | // 启动流程仍然是MainActivity --> A --> B |
singleInstance
系统会启动一个新的任务来装载该 Activity,并且该 Activity 始终是其任务唯一仅有的成员,也就是 Activity 和任务是一一对应的。
1 | // 启动顺序 MainActivity --> ActivityLifeCycleA --> ActivityLifeCycleB |
注意:
进入顺序为MainActivity --> ActivityLifeCycleA --> ActivityLifeCycleB
但是退出顺序为ActivityLifeCycleB --> MainActivity --> ActivityLifeCycleA
从 Log 中可以看出,MainActivity 和 ActivityLifeCycleB 是在同一个任务中的返回栈中,所以按返回键会先退出 ActivityLifeCycleB,然后按照栈的顺序进入 MainActivity,再次按返回键退出 MainActivity 后,前台任务完全退出,进入 ActivityLifeCycleA 所在的任务。
总结
- 实例数,
standard标准模式会开启多个实例,singleTop有条件单个实例,singleTask和singleInstance都是单个实例(单例模式)。 taskAffinity决定了singleTask是否开启新的任务
启动模式引入新的方法 onNewIntent
定义
protected void onNewIntent(Intent intent) {}
如果 Activity 已经实例化但需要重新加载时,会先执行 onNewIntent,然后再执行生命周期函数。
示例
singleTop中,如果B已经在栈顶,执行顺序B:: onPause --> B:: onNewIntent --> B:: onResumesingleTask和singleInstance中,如果A已经存在于任务中,则再次启动A时,执行顺序A:: onNewIntent --> A:: onRestart --> A:: onStart --> A:: onResume
注意事项
重写 onNewIntent,必须调用 setIntent 来更新 intent,否则默认 getIntent 还是拿到之前的 intent。参考源码注释:
1 | * <p>Note that {@link #getIntent} still returns the original Intent. You |
使用 Intent 标志定义启动模式
启动 Activity 时,可以通过在传递给 startActivity() 的 Intent 中加入相应的标志,修改 Activity 与其任务的默认关联方式。
注意:
Intent中设置的标志优先级高于Activity在清单文件中的定义
可用于修改默认行为的标志包括:
- FLAG_ACTIVITY_NEW_TASK
如果taskAffinity的任务存在,则不用新开任务,不管该Activity是否存在都会创建新实例。如果不存在则新开任务,比如在BroadcastReceiver中开启Activity就需要使用该标志 - FLAG_ACTIVITY_SINGLE_TOP
与singleTop启动模式有相同的行为 - FLAG_ACTIVITY_CLEAR_TOP
如果Activity已在当前任务中运行,则会销毁当前任务返回栈中顶部的所有Activity,直到该Activity位于栈顶
注意事项
- 屏幕旋转,需要有平滑的用户体验
- 用户数据在
Activity跳转时不能丢失 - 特定场景下进程可能会被杀掉·