Activity的生命周期和启动模式

Activity的生命周期和启动模式

Activity生命周期和启动模式

Activity的生命周期

Activity生命周期

正常情况下的Activity生命周期

正常情况下,Activity会经历如下生命周期

onCreate(Bundle savedInstanceState) –创建

表示Activity正在被创建,是生命周期的第一个方法

可以做一些初始化工作,调用setContentView()加载布局,初始化Activity需要的数据

onCreate 入参的 savedInstanceState其实就是 由于Activity异常销毁存储下来的数据

onRestart() – 重启

表示Activity正在重新启动,当当前Activity从不可见切换到可见时,就会触发

随后就会调用到onStart()方法

onStart() – 可见

表示Activity正在启动,这时Activity已经可见了,但没有出现在前台无法与用户交互

onResume() –可交互

表示Activity已经可见了,并且出现在前台且可以与用户交互。

onPause() – 不可交互

表示Activity正在停止,此时可以做一些存储数据、停止动画等操作

onPause 中不能执行过于耗时操作,会影响到下一个新Activity的显示。旧Activity必须执行完onPause()后,新Activity的onResume()才可以执行。

onStop() – 不可见

表示Activity即将停止,此时Activity已经不可见,可以做一些稍微重量级的回收工作(取消网络连接,注销广播监听器等),同样不能太耗时。

当新Activity为透明主题即旧Activity依然可见,或者弹出一个框,都不会执行旧Activity的onStop()

onDestroy() – 销毁

表示Activity即将被销毁,这时可以做一些回收工作以及资源的释放。

拓展

Activity在处于onPause(),onStop(),onDestroy()状态下,进程优先级较低,容易被回收,所以需要保存一些数据时,必须在onPause中存储,其他两个周期不一定能调用到。

生命周期状态机与回调保证(补充)

从系统调度角度看,Activity生命周期并不是“每个回调都必然完整执行”。

  • onCreate -> onStart -> onResume是进入前台的标准路径。
  • onPause通常是离开前台的第一站,且应保持轻量。
  • onStop/onDestroy在进程被系统直接回收时,可能来不及回调。

资源管理建议按“可丢失程度”分层:

  1. 前台敏感资源(动画、输入、相机预览)在onPause及时收敛。
  2. 可延后释放资源(监听器、缓存引用)在onStop处理。
  3. 最终兜底释放(大对象、线程)在onDestroy执行,但不要只依赖这一步。

这样可以避免“只在onDestroy回收导致偶发泄漏”的问题。

Activity生命周期的阶段

可以分为以下3个阶段:

  • 完整生命周期

    onCreate() 初始化开始直到onDestroy() 释放资源结束

  • 可见生命周期

    onStart() 可见onStop() 不可见结束

  • 前台生命周期

    onResume() 可交互onPause() 无法交互结束

Activity生命周期的切换过程

  • 启动Activity

    onCreate() -> onStart() -> onResume()

  • 打开新的Activity

    旧Activity.onPause() -> Activity.onCreate() -> Activity.onStart() -> Activity.onResume()-> 旧Activity.onStop()

  • 返回上一个Activity

    新Activity.onPause() -> 旧Activity.onRestart() -> 旧Activity.onStart() -> 旧Activity.onResume() -> 新Activity.onStop() -> 新Activity.onDestroy()

  • 弹出对话框

    • 自身调用弹出 不会有生命周期变化
    • 外部调用弹出 当前Activity.onPause()
  • 关闭屏幕/按Home键

    • onPause() -> onStop()
    • 新Activity显示,旧Activity依然可见 新Activity.onPause() -> 新Activity.onStop() -> 旧Activity.onStop()
  • 点亮屏幕/回到应用

    • onRestart() -> onStart() -> onResume()
    • 新Activity显示,旧Activity依然可见 新Activity.onRestart() -> 新Activity.onStart() -> 旧Activity.onRestart() -> 旧Activity.onStart() -> 新Activity.onResume()
  • 销毁Activity

    • onPause() -> onStop() -> onDestroy()
    • 新Activity显示,旧Activity依然可见,销毁新Activity 新Activity.onPause() -> 旧Activity.onResume() -> 旧Activity.onStop() -> 旧Activity.onDestroy()
  • 弹出对话框样式的Activity

    旧Activity.onPause() -> 新Activity.onCreate() -> 新Activity.onStart() -> 新Activity.onResume()

  • 状态栏下拉

    不会有生命周期变化,如果需要监听可以 使用 onWindowFocusChanged()

  • 横竖屏切换下的生命周期

    这是一个 销毁重建的过程

    onPause() -> onStop() -> onDestroy() -> onCreate() -> onStart() -> onResume()

特殊场景生命周期矩阵(补充)

场景 典型回调 说明
打开透明/对话框样式Activity 旧页onPause,通常不onStop 旧页仍可见但失去交互
按 Home 键回桌面 onPause -> onStop 页面进入后台但实例仍可能存活
最近任务切回应用 onRestart -> onStart -> onResume 取决于是否已被系统回收
锁屏再解锁 通常表现为onPause/onStop后恢复 不同 ROM 可能有差异
下拉状态栏 一般无生命周期变化 更适合用onWindowFocusChanged感知焦点

说明:多窗口和分屏模式下,“可见”和“可交互”会进一步解耦,单靠一个回调判断前后台并不稳妥。

异常情况下的生命周期分析

Activity除了上述正常情况下执行的生命周期调度,还会有一些异常情况会导致Activity被杀死。

例如在执行到onPause()onStop()时,Activity进入了Finish状态,表示被异常终止。

由于资源相关的系统配置发生改变导致Activity被杀死并重新构建

例如:当Activity发生横竖屏切换时,发了系统配置的改变,在默认情况下Activity就会被销毁并重建。

如何避免配置改变导致Activity重新创建?

可以在AndroidManifest.xml中指定对应的系统属性,这样在触发对应改变时,不会再杀死并重建,会调用到onConfigurationChanged(),只需重写该方法即可。

例如配置了android:configChanges="orientation",横竖屏切换时就不会触发重建。

由于系统资源不足,导致优先级低的Activity被杀死

这里需要先了解Activity的优先级情况。按照从高到低分为以下三种:

  1. 前台Activity:正在和用户交互的Activity
  2. 可见但非前台Activity:前台的Activity弹出一个Dialog,导致无法交互
  3. 后台Activity:已经被暂停的Activity,比如切到后台或者切换应用

当系统内存不足时,系统就会按照上述描述的优先级去杀死目标Activity所在进程。

如果一个进程中没有四大组件在执行,进程很快被系统杀死。

当上述两种情况发生时,Activity的生命周期会发生如下变化:

  • Activity被杀死:

    Android 9.0之前onPause() -> onSaveInstanceState() -> onStop() -> onDestroy()

    Android 9.0之后onPause() -> onStop() -> onSaveInstanceState() -> onDestroy()

    系统异常终止时,调用onSaveInstanceState()保存数据。该方法调用在onStop()之前。

    保存数据过程是利用一种委托的思想,上层委托下层。

  • Activity重建:onCreate() -> onStart() -> onRestoreInstanceState() -> onResume()

    重新创建时,调用onRestoreInstanceState(),调用在onStart()之后,该方法会把onSaveInstanceState()存储的Bundle对象拿出来解析。

    onCreate和onRestoreInstanceState都可以获取存储的对象,推荐使用onRestoreInstanceState不需要额外的去判断是否为空。

系统只有在异常终止的情况下才会调用onSaveInstanceState和onRestoreInstanceState进行存储和恢复数据。

补充:

  • onSaveInstanceState主要面向“重建恢复”,不是通用持久化入口。
  • 进程被直接杀死后,未持久化到本地的数据依然会丢失。
  • 因此关键业务数据应落到DB/SP/File,Bundle只保留轻量UI状态。

状态保存与恢复分层(补充)

建议把状态分成三层处理,避免把所有数据都塞进Bundle

状态类型 推荐载体 生命周期范围
UI瞬时状态(滚动位置、选中态) onSaveInstanceState Activity重建可恢复
页面内业务态(列表数据、加载状态) ViewModel 配置变更可保留
关键持久数据(草稿、离线数据) 本地持久层(DB/SP/File) 进程重启后可恢复

时序上,Android 9.0 前后在onStoponSaveInstanceState先后顺序有差异,因此业务逻辑不要依赖它们的相对顺序,而应保证保存操作本身幂等。

另外,onSaveInstanceState不适合存储大对象:

  • 传输成本高,恢复慢;
  • 过大可能触发事务异常;
  • 容易把“可重算数据”误当“必须保存数据”。

拓展:

  1. 还有一些会在Activity运行过程中的触发方法,这里简单的提及一下:
    • onPostCreate():在onCreate()执行完毕后回调
    • onUserInteraction():所有Activity上的触摸事件 优先调用该方法
    • onUserLeaveHint():用户主动离开Activity调用该方法,例如点击Home
    • onContentChanged():Activity 调用setContentView()完成后调用

Activity的启动模式

任务与实例模型(补充)

启动模式相关问题,本质可以拆成两层:

  1. 实例层:这次启动是复用旧 Activity,还是新建 Activity。
  2. 任务层:这次启动是在现有 Task 中入栈,还是切到/创建另一个 Task。

这两层是独立决策:

  • “复用实例”不一定“切换任务栈”;
  • “切换任务栈”也不一定“复用实例”。

把这两个维度分开理解,很多启动模式问题会更清晰。

补充:排查“为什么没复用/为什么被清栈”时,可按固定顺序定位:

  1. 先看Intent Flag是否改变了任务栈选择。
  2. 再看launchMode是否允许实例复用。
  3. 最后看目标实例是否处于栈顶或已存在于目标Task中。

Activity的任务栈

当我们多次启动一个Activity的时候,系统会创建多个实例并放入任务栈中,当我们触发finish时,Activity会一一回退。任务栈是一种先进后出的栈结构。

任务栈又分为前台任务栈后台任务栈后台任务栈中的Activity位于暂停状态.

  • 程序在创建时就会创建一个Activity任务栈,存储当前程序的Activity
  • 任务栈是Activity的集合,只有位于栈顶的Activity可以和用户交互
  • 任务栈可以移动到后台并保留了Activity的状态
  • 退出应用程序时,任务栈会被清空,然后会被系统回收。

利用adb shell dumpsys activity查看当前任务栈

Activity的LaunchMode

LaunchMode是为了减少Activity实例的重复创建,并优化任务栈行为

使用方法:

  • AndroidManifest.xml中给对应Activity配置属性 android:launchMode="standard | singleTop | singleTask | singleInstance"
  • startActivity时添加intent.addFlags(FLAG)

launchMode行为对照表(补充)

模式 是否可能新建实例 复用条件 是否可能触发onNewIntent 任务栈特性
standard 总是新建 跟随调用方任务栈
singleTop 栈顶时不新建 目标Activity已在栈顶 仍在当前任务栈内处理
singleTask 已存在时不新建 目标Activity在任务栈中存在 复用目标并清理其上方实例
singleInstance 已存在时不新建 全局仅保留该实例 独立任务栈,仅该实例存在

该表用于先判断“实例复用”,再判断“任务栈变化”,比只记结论更稳妥。

standard 标准模式(默认这个)

每次启动一个新的Activity都会创建一个新的Activity实例。

若启动Activity的是除了Activity之外的context对象就需要指定FLAG_ACTIVITY_NEW_TASK标记位,创建一个新的任务栈。因为standard默认进入启动方的任务栈,由于他们是没有自身的任务栈,所以需要新建。

alt

singleTop 栈顶复用模式

如果要启动的Activity位于栈顶,就不会重新创建,并且调用onNewIntent(Intent intent)取出当前请求的信息。

还会调用onPause()以及onResume()

alt

A位于栈顶,B位于栈底。如果A的启动模式为singleTop,再次启动A,栈内情况不会发生变化,依然为AB

如果启动B,则会创建新的实例,不论是否为singleTop

singleTask 栈内复用模式

栈内只要存在Activity实例,再次启动都不会重新创建实例,只会回调onNewIntent(),并从栈内弹出该实例上的所有Activity。

适合作为应用主入口,因为只会启动一次。

补充:这里的“清理其上方页面”本质是clearTop语义,目标实例本身会被保留并移到前台继续承载新Intent。

列举3个实例加深理解:

  • alt

    目前S1中由ABC三个实例,这时D以singleTask模式请求启动且所需任务栈为S2,由于S2D实例均不存在,所以系统会创建S2任务栈并把实例D入栈到S2

  • alt

    目前S1中由ABC三个实例,这时D以singleTask模式请求启动且所需任务栈为S1,由于S1已经存在,所以直接入栈并置于栈顶。

  • alt

    目前S1中由ABCD四个实例,这时B以singleTask模式请求启动且所需任务栈为S1,此时B不会重新创建,将直接回调onNewIntent()并置于栈顶。原先位于B实例上的CD都被清除,因为默认具有clear_top 效果,最终就变成了AB

singleInstance 单实例模式

加强的singleTask模式,除了singleTask拥有的特性外,还加强了一点。使用了这个模式启动的Activity只能单独的位于一个任务栈中。启动时会新开一个任务栈并直接创建实例压入栈中。

即使设置了相同的任务栈名,也不能放在一个栈中。

onNewIntent回调语义(补充)

当系统选择“复用已有实例”时,会把新的 Intent 通过onNewIntent分发给该实例。

常见触发场景:

  • singleTop且目标位于栈顶;
  • singleTask/singleInstance且目标实例已存在。

使用建议:

  1. onNewIntent里解析新参数并更新页面状态;
  2. 调用setIntent(intent)保持getIntent()与最新请求一致;
  3. 对同一参数重复到达做幂等处理,避免重复执行业务动作。

补充:

  • 若页面内部依赖SavedStateHandle/Fragment状态,也要同步更新对应状态源。
  • 仅更新Intent但不刷新UI,会出现“参数已变、界面未变”的状态错位。

TaskAffinity – 栈亲和性

taskAffinity:标识了一个Activity所需要任务栈的名字,默认情况下,所有Activity所需的任务栈名字为应用的包名。我们也可以为每个Activity指定任务栈,利用android:taskAffinity属性标记。

  • 配合singleTask使用

    新Activity启动时默认被加载进启动该Activity的对象所在任务栈中。如果给启动的Activity设置FLAG_ACTIVITY_NEW_TASK标记或者设置singleTask启动模式,再配合taskAffinity设置任务栈名字,该实例就会被加载进相同名字的任务栈中,如果不存在相同就创建新的任务栈并压入实例。

  • 配合allowTaskReparenting使用

    allowTaskReparenting 作用是 是否允许Activity更换从属任务。true表示可以更换,默认为false

    简单描述: 有两个APP,A和B,此时应用A去启动应用B中的一个Activity,并且该Activity设置allowTaskReparenting = true,此时这个Activity的任务栈就会位于应用A中,当去启动B时,会优先展示已被启动的Activity,由于设置了allowTaskReparenting该Activity的任务栈又回到了B中。

allowTaskReparenting真实迁移时机(补充)

这个属性并不是“启动后立刻迁移任务栈”,而是满足条件后在任务切换过程中发生重归属。

可按以下顺序理解:

  1. Activity先被临时放入当前调用方任务栈。
  2. 当其“目标亲和任务栈”回到前台时,系统判断是否允许重归属。
  3. 条件满足则把该 Activity 从当前 Task 移动到目标 Task。

因此它更像“延迟重挂载机制”,而不是一次性的启动策略。

拓展知识:

Activity的行为标志和属性

Activity的Flag

有些标记位可以设置启动模式,还有的可以影响Activity的运行状态。

FLAG_ACTIVITY_NEW_TASK

常见效果接近singleTask,但两者不完全等价。

补充:

  • FLAG_ACTIVITY_NEW_TASK优先影响“任务栈选择/创建”。
  • singleTask同时约束“实例复用规则(任务栈内唯一实例)”。
  • 两者叠加时,通常表现为“先选Task,再按复用规则处理实例”。
FLAG_ACTIVITY_SINGLE_TOP

作用等同singleTop启动模式

FLAG_ACTIVITY_CLEAR_TOP

当用这个标记启动对应Activity时,在同一个任务栈中的且位于它上面的Activity实例都会被消除。一般配合singleTask使用

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

对应的配置为在AndroidManifest.xml中使用android:excludeFromRecents="true"

具有这个标记的Activity不会出现在后台任务列表中

常用Flag组合语义(补充)

  • NEW_TASK + CLEAR_TOP

    在目标任务栈中把目标 Activity 之上的实例清理后复用或重建目标实例,常用于“回到主路径页面”。

  • NEW_TASK + CLEAR_TASK

    先清空目标任务栈再放入新启动页面,常用于“重置导航历史”。

  • SINGLE_TOP + CLEAR_TOP

    如果目标已在栈内,优先复用并触发onNewIntent,同时清理其上方页面。

  • NEW_TASK + EXCLUDE_FROM_RECENTS

    创建独立任务但不出现在最近任务列表,适合短期工具页或中转页。

IntentFilter的匹配规则

启动Activity方法分为两种:显式调用(可以清楚指出被启动组件的信息,例如类名)隐式调用(没有明确的指出组件信息,通过IntentFilter找到符合要求的组件)

匹配规则:

  1. 一个intent只有同时匹配某个Activity定义的<intent-filter>中定义的action,category,data才可以完全匹配,打开对应的Activity
  2. 一个Activity可以定义多个,只要匹配任意一组就可以启动该Activity
action匹配规则

只要传递过来的Intent中定义的action可以匹配<intent-filter>定义的任一action,必须要完全相同且区分大小写。

category匹配规则

传递过来的Intent中不包含category,那么就会启用默认的category,由于系统在启动Activity的时候默认会加上android.intent.category.DEFAULT属性

如果包含category,那必须匹配<intent-filter>定义的任一category

data匹配规则

传递过来的Intent定义的data可以匹配<intent-filter>定义的任一data

data主要分为两部分:

  • mimeType:媒体类型,例如text/plain这类,还包括图片,视频类型
  • URL:地址 包含了host(主机名),scheme(模式),port(端口号),path(路径信息)
data字段匹配优先级(补充)

可以按“约束由强到弱”理解匹配关系:

  1. scheme是第一层门槛(如httpcontent)。
  2. scheme匹配后,再看host/port/path是否满足。
  3. mimeType与 URI 匹配共同决定最终是否命中。

任一关键字段不满足,都可能导致隐式启动匹配失败。

启动前校验建议(补充)

隐式启动前建议先做一次可达性校验:

  • 通过resolveActivity确认是否有可处理组件;
  • 对外部 URI 做基础合法性检查(空值、非法 scheme);
  • 对关键业务链路准备降级页面,避免直接抛出异常。

隐式启动时,如果无法找到要启动的组件,就会抛出异常。我们就可以利用PackageManager.resolveActivity()或者Intent.resolveActivity()避免异常出现。

清理任务栈

当用户离开一个任务时间很长时,系统将会清除除了根Activity之外的所有Activity,当用户重新回到应用时,只能看到根Activity。

系统提供了几种机制来调整这个规则:

  • android:alwaysRetainTaskState

    标记应用的Task是否保持原来的状态,若为true,系统尝试保留所有Activity

  • android:clearTaskOnLaunch

    标记是否从Task清除所有Activity除了根Activity,用户每次重新打开只会看到根Activity

  • android:finishOnTaskLaunch

    只作用于单个Activity,若设置true,用户离开后回来就会消失

任务栈清理属性对照(补充)

属性 作用范围 典型效果
alwaysRetainTaskState 整个 Task 尽量保留离开前的栈状态
clearTaskOnLaunch 整个 Task 重新进入时仅保留根Activity
finishOnTaskLaunch 单个 Activity 任务重回前台时该页面可被移除

这三个属性可以叠加使用,最终行为由“Task级规则 + Activity级规则”共同决定。

启动模式源码分析

关键节点在 ActivityStarter.java类下

核心判定路径(补充)

启动请求进入系统后,通常会经历这几步:

  1. 解析 Intent 与调用参数(launchMode、flag、taskAffinity)。
  2. 判断是否可复用已有 Activity 实例。
  3. 判断是否复用现有 Task,或创建/切换到目标 Task。
  4. 若复用实例则分发onNewIntent,若不复用则创建新实例。

也就是说,启动模式源码核心是“实例复用判定 + 任务栈归属判定”。

standard

standard分支最直接:默认创建新实例并压入当前任务栈。

  • 如果调用方不是 Activity(例如 Application Context),通常需要NEW_TASK补足任务栈语义。
  • 该模式下不会触发实例复用路径,因此一般不会进入onNewIntent

singleTop

singleTop关键在“仅栈顶可复用”:

  • 目标 Activity 已在栈顶:复用实例并回调onNewIntent
  • 目标 Activity 不在栈顶:仍会新建实例入栈。

因此它只解决“重复点击栈顶页面”问题,不解决“栈内任意位置复用”。

singleTask

singleTask关键在“任务栈内唯一实例”:

  • 若目标实例已在任务栈中,系统会复用该实例并清理其上方页面。
  • 若不存在,创建新实例(可能伴随任务栈切换或新建)。

它同时影响“实例复用”和“任务栈行为”,比singleTop影响范围更大。

singleInstance

singleInstance是在singleTask基础上的进一步约束:

  • 目标 Activity 独占一个任务栈;
  • 该任务栈中通常不会再放入其他页面。

该模式适合强隔离页面,但会增加任务切换复杂度,使用应谨慎。

版本差异映射(补充)

该文主线基于较早版本源码。后续 Android 版本演进中,Activity/Task 调度职责有明显迁移:

  • Activity 与 Task 的调度职责逐步从 AMS 中拆分到 ATMS 相关链路;
  • 多窗口、分屏等场景下,“可见”与“前台可交互”的语义更细化;
  • 新版本对任务切换、后台启动限制、导出组件校验等约束更严格。

阅读新版本源码时,建议先按“职责类迁移”定位入口,再对照本文的调度阶段理解流程。

拓展

  1. 何时会调用onNewIntent()?

    • LaunchMode设置为singleTop,且要启动的Activity已经处于栈顶
    • LaunchMode设置为singleTask或者singleInstance,且实例已存在

    需要注意的是:当调用到onNewIntent(intent)的时候,需要在内部调用setIntent(intent)更新当前Activity的Intent,否则后续的getIntent()得到的都是旧Intent。

  2. 监控应用回到桌面或者应用退出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
    int createdActivityCount = 0;
    int startedActivityCount = 0;

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    createdActivityCount++;
    }

    @Override
    public void onActivityStarted(Activity activity) {
    startedActivityCount++;
    }

    @Override
    public void onActivityResumed(Activity activity) {

    }

    @Override
    public void onActivityPaused(Activity activity) {

    }

    @Override
    public void onActivityStopped(Activity activity) {
    startedActivityCount--;
    // isChangingConfigurations 避免因为应用配置变化导致的退出使统计失误
    if (startedActivityCount == 0 && !activity.isChangingConfigurations() && !activity.isFinishing()) {
    Log.e("Home", "回到桌面");
    }
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    createdActivityCount--;
    if (createdActivityCount == 0 && !activity.isChangingConfigurations()) {
    Log.e("Exit", "应用退出");
    }
    }
    });
  3. s


Activity的生命周期和启动模式
https://leo-wxy.github.io/2019/01/04/Activity的生命周期和启动模式/
作者
Leo-Wxy
发布于
2019年1月4日
许可协议