Activity生命周期和四大启动模式详解
一、Activity 生命周期
Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机:
-
onCreate()
- 调用时机:Activity 首次创建时调用。
- 作用:初始化布局(
setContentView
)、绑定数据、创建后台线程等。 - 注意:在此方法中应避免耗时操作。
-
onStart()
- 调用时机:Activity 可见但未获得焦点(例如被对话框覆盖)。
- 作用:恢复UI更新或资源加载。
-
onResume()
- 调用时机:Activity 进入前台并可与用户交互。
- 作用:启动动画、传感器监听、高频率更新UI等。
- 关键点:此时 Activity 位于栈顶。
-
onPause()
- 调用时机:Activity 失去焦点(如弹出对话框或跳转到其他 Activity)。
- 作用:保存临时数据、释放资源(如摄像头)。
- 注意:需快速执行,否则会影响新 Activity 的启动。
-
onStop()
- 调用时机:Activity 完全不可见(被其他 Activity 覆盖或退出)。
- 作用:停止动画、释放非必要资源。
-
onDestroy()
- 调用时机:Activity 被销毁(用户主动退出或系统回收资源)。
- 作用:清理内存、注销广播等。
-
onRestart()
- 调用时机:Activity 从停止状态重新回到前台(如按返回键返回)。
- 流程:
onRestart()
→onStart()
→onResume()
。
场景应用
场景 1:打开新页面
流程:
- 原 Activity 执行
onPause()
(失去焦点) - 新 Activity 依次执行:
onCreate()
(初始化)onStart()
(可见)onResume()
(可交互)
- 原 Activity 执行
onStop()
(完全不可见)
典型场景:
- 从主页跳转到详情页
- 列表页打开新的商品页
场景 2:返回上一个页面
流程:
- 当前 Activity 执行
onPause()
- 上一个 Activity 依次执行:
onRestart()
(重新激活)onStart()
(可见)onResume()
(可交互)
- 当前 Activity 执行:
onStop()
onDestroy()
(被销毁)
典型场景:
- 提交订单后返回购物车
- 查看图片详情后返回相册
场景 3:屏幕旋转
流程:
onPause()
→onSaveInstanceState()
(保存数据)onStop()
→onDestroy()
(销毁实例)- 重新创建:
onCreate()
(携带保存的数据)onStart()
onRestoreInstanceState()
(恢复数据)onResume()
开发重点:
- 在
onSaveInstanceState()
保存编辑框内容/滚动位置 - 避免在旋转时中断网络请求
场景 4:被弹窗/来电中断
场景 | 生命周期变化 | 恢复顺序 |
---|---|---|
普通对话框 | onPause() | onResume() |
全屏弹窗/来电 | onPause() → onStop() | onRestart() → onResume() |
关键区别:
- 是否完全遮挡决定是否触发
onStop()
二、四大启动模式(Launch Mode)
Android 的四大启动模式核心基于任务栈管理机制实现。系统通过 ActivityManagerService(AMS)维护一个全局的任务栈结构,每个任务栈由 TaskRecord 对象表示,遵循后进先出的堆栈原则。当启动 Activity 时,AMS 会根据不同启动模式的预设规则,动态调整任务栈的组成。
standard 模式是最基础的方式。它简单地无条件创建新 Activity 实例,并将其压入当前任务栈顶部。这种模式不涉及任何复用判断,因此可能在栈中积累多个相同 Activity 实例,对内存管理构成压力。系统在处理时直接调用 ActivityStarter 的强制压栈方法,整个过程没有任何条件检测。
适用场景:
- 常规页面跳转(如商品详情页、新闻内容页)
- 多实例并行需求(如浏览器标签页、文档编辑页)
典型案例: - 电商应用浏览商品:
主页 → 商品A页 → 商品B页
,栈内存在多个不同商品页实例 - 文档应用编辑文件:同时打开
文档1
、文档2
,返回时可逐个切换
singleTop 模式的核心在于栈顶检查机制。启动时,AMS 会立即判断目标 Activity 是否恰好位于当前栈顶。如果匹配成功,则直接触发该实例的 onNewIntent() 进行数据刷新,完全跳过创建流程;若不在栈顶,则回退到 standard 模式创建新实例。这种设计特别适合通知栏点击等高频重复启动的场景。
适用场景:
- 避免重复通知(通知栏多次点击跳转同一页)
- 高频操作入口(如刷新按钮、重复提交页面)
典型案例: - 微信消息页面:当停留在
聊天页A
时收到新消息,点击通知直接刷新当前页面 - 支付倒计时页:用户多次点击“重新支付”按钮,复用当前页面更新倒计时
特殊场景: - 扫二维码页面:已打开扫码页时再次触发扫码,直接复用而非新建
singleTask 模式采用任务栈重构策略。系统会优先搜索与目标 Activity 的 taskAffinity 匹配的任务栈,检测是否存在已有实例。如果找到,AMS 会先清除该实例之上的所有 Activity(触发它们的 onDestroy),将目标实例置于栈顶并调用 onNewIntent();若未找到,则新建独立任务栈。这种清除机制有效保障了栈内实例唯一性,但处理成本较高。
适用场景:
- 应用主页(如微信主界面、设置首页)
- 登录/授权中转站(确保返回时跳过登录页)
典型案例: - 支付流程结束:
商品页 → 收银台 → 支付成功页
,点击“返回首页”清空整个支付栈,直达主页 - 社交应用登录:若已登录过,再次打开APP直接进入
主页面
而非登录页
singleInstance 模式具有全局隔离特性。该模式强制目标 Activity 独占整个任务栈,且栈内不允许其他 Activity 存在。启动时,AMS 先全局搜索专属栈:若存在则直接提至前台复用;若不存在则创建带唯一 ID 的新栈。这种模式实现了跨应用级的隔离,在系统低内存回收时可能被独立保留,常用于相机、拨号等系统组件。
适用场景:
- 系统级共享组件(相机、通讯录选择器)
- 独立功能模块(如浮窗通话界面)
典型案例: - 系统相机调用:应用中启动相机拍照,返回时直接回到原应用
- 第三方分享面板:微信分享时调起系统分享栈,完成后自动销毁
特殊注意: - 银行应用安全键盘:通过独立进程防止密码输入被截屏
场景需求 | 推荐模式 |
---|---|
常规页面跳转(允许多实例) | standard |
避免栈顶重复(如通知点击) | singleTop |
应用入口/核心页(需清栈) | singleTask |
独立系统组件(跨进程调用) | singleInstance |
通过 AndroidManifest.xml
或 Intent 标志(如 FLAG_ACTIVITY_NEW_TASK
)指定,控制 Activity 实例与任务栈(Task)的关系。
onNewIntent()
是 Activity 被复用时的数据刷新入口,用于处理同一个 Activity 实例被再次启动时的新意图(Intent),而无需重建页面。
核心作用(3个关键点):
-
复用已有页面
- 避免重复创建相同 Activity(节省内存)
- 保持当前页面状态(如滚动位置、输入内容)
-
更新数据
protected void onNewIntent(Intent intent) { setIntent(intent); // 必须更新!否则getIntent()拿旧数据 refreshUI(intent); // 根据新数据刷新界面 }
-
特定场景触发
- singleTop:当 Activity 位于栈顶时
- singleTask/singleInstance:当 Activity 已存在于栈中时
典型场景:
- 点击通知栏多次打开同一聊天页(直接刷新消息)
- 从支付结果页返回后再次支付(复用页面更新订单)
- 全局搜索框重复搜索(保留搜索历史记录)
记住一个原则:
只要看到 singleTop
/singleTask
,就要考虑是否需要重写 onNewIntent()
处理数据刷新!
启动模式在后台运行时的关键行为解析
一、后台启动 Activity 的通用规则
-
基本行为:
- 当应用在后台时启动新 Activity,系统会先将应用带回前台
- 新 Activity 会被添加到当前任务栈中
- 用户按返回键时会回到上一个 Activity
-
核心影响:
- 应用进程优先级提升到前台进程
- 可能触发系统回收机制(低内存时后台进程优先被杀)
二、后台启动的注意事项
1. Android 8.0+ 限制
- 后台启动限制:应用在后台时无法随意启动 Activity
- 解决方案:
// 必须使用全屏通知 NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentIntent(pendingIntent) // 用户点击才启动 .setFullScreenIntent(pendingIntent, true); // 紧急通知
2. 内存回收策略
启动模式 | 回收优先级 | 恢复难度 |
---|---|---|
standard | 高 | 难(多实例) |
singleTop | 中 | 中等 |
singleTask | 低 | 易(单实例) |
singleInstance | 最低 | 最易 |
3. 最佳实践
-
后台启动 singleTask 主页:
// 清理所有历史栈 Intent intent = new Intent(context, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-
避免后台 standard 启动:
- 易导致 OOM(内存溢出)
- 返回栈混乱
-
跨进程通信:
// 启动独立进程的Activity <activity android:process=":camera_process"/>
三、各模式后台表现对比表
启动模式 | 后台启动特点 | 内存效率 | 适用场景 |
---|---|---|---|
standard | 持续堆叠新实例 | 低 | 需多实例的普通页面 |
singleTop | 栈顶复用省资源 | 中 | 通知/消息更新 |
singleTask | 清理栈内多余页面 | 高 | 应用主页/核心入口 |
singleInstance | 独立进程不受主应用影响 | 最高 | 相机/电话等系统级功能 |
模式 | 实例数量 | 栈位置 | 典型场景 |
---|---|---|---|
standard | 多个 | 当前栈 | 普通页面 |
singleTop | 栈顶唯一 | 当前栈 | 防重复启动(通知栏) |
singleTask | 栈内唯一 | 可指定新栈 | 应用主界面 |
singleInstance | 全局唯一 | 独占新栈 | 独立功能(如相机) |
微信小游戏双图标背后的启动模式解析
场景重现:
-
点击微信图标启动微信主界面
-
在微信内点击进入小游戏
-
后台出现两个独立图标:
-
微信主应用图标
-
小游戏独立图标
-
核心启动模式:singleInstance
实现原理:
<!-- 小游戏Activity声明示例 -->
<activity
android:name=".GameActivity"
android:launchMode="singleInstance"
android:taskAffinity="com.tencent.game"
android:process=":game_process" />
三大关键机制:
-
独立进程
android:process=":game_process"
-
小游戏运行在独立的
com.tencent.mm:game_process
进程 -
与微信主进程
com.tencent.mm
完全隔离 -
效果:系统显示两个独立进程图标
-
-
独立任务栈
android:taskAffinity="com.tencent.game" android:launchMode="singleInstance"
-
创建专属任务栈(与微信主栈隔离)
-
效果:
-
最近任务显示两个独立任务项
-
游戏退出时直接回到手机桌面,不经过微信
-
-
-
跨进程通信
// 微信启动游戏的关键代码 Intent intent = new Intent(); intent.setComponent(new ComponentName("com.tencent.mm", "com.tencent.mm.plugin.game.GameActivity")); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); startActivity(intent);
-
使用
FLAG_ACTIVITY_MULTIPLE_TASK
允许多实例
-
完整启动流程:
-
用户点击小游戏入口
-
微信创建子进程:
-
通过
startActivity()
跨进程启动 -
指定
singleInstance
+NEW_TASK
标志
-
-
系统创建资源隔离区:
-
独立内存空间(分配专属内存)
-
独立渲染线程(避免微信主线程卡顿)
-
独立任务栈(系统记录为独立应用)
-
-
游戏结束后:
-
游戏进程销毁
-
微信主进程不受影响
-
返回路径:游戏 → 桌面 (不返回微信)
-
技术优势:
-
性能隔离:
-
游戏占500MB内存不影响微信聊天
-
游戏崩溃不会导致微信闪退
# 进程内存占用示例 com.tencent.mm: 300MB # 微信主进程 com.tencent.mm:game: 500MB # 游戏进程
-
-
独立生命周期:
操作
微信主进程
游戏进程
进入游戏
onPause()
onCreate()
游戏切后台
-
onPause->onStop
关闭游戏
onResume()
onDestroy()
游戏崩溃
无影响
自动重启
-
用户体验优化:
-
游戏可独立操作(微信后台保持运行)
-
小窗口模式双向互动(微信浮窗+游戏)
-
对比其他启动模式:
启动模式 | 是否分进程 | 是否独立图标 | 适用场景 |
---|---|---|---|
standard | ❌ | ❌ | 普通页面跳转 |
singleTask | ❌ | ❌ | 微信钱包 |
singleTop | ❌ | ❌ | 公众号文章 |
singleInstance | ✅ | **✅** | 小游戏/视频通话 |
典型应用场景:
-
微信/QQ内置小游戏
-
直播平台连麦功能
-
银行App的安全键盘
-
AR扫描模块
ContentProvider应用场景
ContentProvider 是 Android 实现应用间安全数据共享(基于 URI 和 Binder)的核心组件,同时通过空实现 Provider 的 onCreate()
机制实现库的自动初始化。
ContentProvider 的主要应用场景
a) 应用间共享数据(最常见目的)
* **系统功能:** Android系统自身提供了大量ContentProvider让应用访问系统数据:
* `ContactsContract.Contacts` (通讯录联系人)
* `MediaStore.Images.Media` (相册图片)
* `MediaStore.Audio.Media` (音频文件)
* `CalendarContract.Events` (日历事件)
* `CallLog.Calls` (通话记录)
* `Settings.System` (系统设置)
* **第三方应用:** 当你的应用需要向其他应用**提供数据**时(如提供用户配置、自定义数据库内容),你需要实现自己的ContentProvider,并定义相应的authority和URI结构。
b) 同一个应用内的结构化数据访问
* 即使只在应用内部使用,ContentProvider也提供了一种**标准化**的方式来访问应用本身的数据库或结构化文件。这有利于统一数据访问逻辑、解耦UI和数据层(符合MVC/MVP/MVVM架构思想)、并便于以后扩展成跨应用共享(仅需添加权限和可能的URI结构调整)。
c) 跨进程回调/协调
* 一些复杂的跨应用协作可以通过ContentProvider的`query()`、`insert()`等操作来触发。例如,应用A通过调用应用B的ContentProvider插入一条特定数据,可以作为一种信号通知应用B执行某项操作(不过Intent广播和Service是更直接的IPC方式)。
d) 应用初始化 - Auto-initialization Providers
* **这是非常关键且容易被问到的点!** 你在开发中可能会遇到一些第三方库(或Android Jetpack库),需要在应用启动(`Application.onCreate()`之前)就立即执行初始化。**ContentProvider 是实现这种“自动初始化”的核心机制。**
* **原理:** 系统会在应用进程启动时,**早于**创建 `Application` 实例并调用其 `onCreate()` 方法之前,先**实例化**并**初始化**(调用 `onCreate()` 方法)清单文件中所有在`<application>`标签里声明的ContentProvider。
* **库的做法:** 一个需要预初始化的库可以在其 SDK 中包含一个**不暴露任何实际数据 URI 的“空” ContentProvider**。
1. 这个 Provider 在清单文件中声明。
2. 它的唯一目的是在其 `onCreate()` 方法中执行库所需的初始化代码(如初始化单例、设置全局配置、数据库设置等)。
3. 由于其 `onCreate()` 调用时机非常早(在 `Application.onCreate()` 之前),库就能确保在应用的任何代码运行前完成初始化。
* **常见例子:**
* **WorkManager (>= v2.1):** 使用 `WorkManagerInitializer` Provider 来自动初始化。
* **Firebase:** `FirebaseApp`的初始化通常通过Provider(如`FirebaseInitProvider`)完成。
* **Google Play Services SDKs:** 某些服务可能使用此机制。
* **Mockito / Robolectric 等测试框架:** 有时用于注入测试依赖。
* **优势:** 开发者只需引入库依赖并配置清单文件(通常是自动合并的),无需在 `Application.onCreate()` 中显式调用 `initialize()` 方法,减少了初始化配置遗漏的可能性。
* **注意:** 过度使用会略微增加启动时间,因为系统需要加载和初始化这些 Provider。应仅用于必要的库初始化。
代码示例:
利用 ContentProvider 实现应用初始化
自定义初始化 Provider 实现 (空内容提供者)
public class AppInitProvider extends ContentProvider {
@Override
public boolean onCreate() {
// 系统在 Application.onCreate() 前调用
initializeApp();
return true; // 初始化成功
}
private void initializeApp() {
// 1. 初始化单例配置
AppConfig.initialize(getContext());
// 2. 设置全局异常处理
Thread.setDefaultUncaughtExceptionHandler(new AppCrashHandler());
// 3. 初始化网络请求库
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.build();
RetrofitManager.init(client);
// 4. 初始化数据库框架
Room.databaseBuilder(
getContext(),
AppDatabase.class,
"app-db"
).fallbackToDestructiveMigration().build();
// 5. 三方库初始化 (示例)
FirebaseApp.initializeApp(getContext());
}
// 空方法 - 此 Provider 不提供实际数据
@Nullable @Override public Cursor query(...) { return null; }
@Nullable @Override public Uri insert(...) { return null; }
@Override public int delete(...) { return 0; }
@Override public int update(...) { return 0; }
@Nullable @Override public String getType(Uri uri) { return null; }
}
AndroidManifest.xml 配置
<application>
<!-- 系统将优先初始化此 Provider -->
<provider
android:name=".init.AppInitProvider"
android:authorities="${applicationId}.appinit" <!-- 唯一标识符 -->
android:exported="false" <!-- 不对外暴露 -->
android:initOrder="100" <!-- 多个 Provider 时指定初始化顺序 -->
tools:ignore="ExportedContentProvider"/>
<!-- 常规 Application 类 -->
<activity
android:name=".MainActivity"
.../>
</application>
Application 类中的后续初始化
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 此时 AppInitProvider 已完成初始化
// 1. 初始化必须依赖前序操作的组件
initializePushNotifications();
// 2. 验证初始化状态
if(!AppConfig.isInitialized()) {
throw new RuntimeException("初始化失败! 请检查AppInitProvider");
}
}
private void initializePushNotifications() {
// 依赖 AppInitProvider 中初始化的网络库
PushService.setHttpClient(RetrofitManager.getClient());
}
}
使用此模式后,开发者只需添加依赖库,零代码初始化三方库 - 特别适合SDK开发者。如 Firebase 通过此机制实现完全自动初始化,无需在 Application 中调用 FirebaseApp.initializeApp()
。
Service 启动方式与实战场景详解
Service是Android执行后台长时操作(如音乐播放、网络下载)或跨进程通信(通过AIDL)的核心组件,其生命周期由系统管理,分为启动状态(startService()
)和绑定状态(bindService()
),8.0+系统需结合前台服务或JobScheduler规避后台限制。
一、两种核心启动方式
1. startService() 启动方式
本质特点:
- 启动后服务与组件完全解耦
- 生命周期独立运行
- 必须显式停止(调用 stopSelf() 或 stopService())
完整生命周期流程:
- 首次启动:
onCreate()
→onStartCommand()
- 后续启动:直接触发
onStartCommand()
- 停止服务:
onDestroy()
场景适用:
- 后台长期任务:如音乐播放、定位追踪
- 无交互任务:如日志上传、数据同步
- 跨应用操作:如推送消息处理
实战代码:
// 启动下载服务
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.putExtra("file_url", "https://example.com/file.zip");
startService(downloadIntent);
// 服务内部停止
public class DownloadService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(() -> {
downloadFile(intent.getStringExtra("file_url"));
stopSelf(); // 下载完成后自动停止
}).start();
return START_NOT_STICKY;
}
}
2. bindService() 启动方式
本质特点:
- 建立组件与服务的绑定关系
- 通过 IBinder 接口实现双向通信
- 绑定解绑自动管理生命周期
完整生命周期流程:
- 首次绑定:
onCreate()
→onBind()
- 通信期间:通过 Binder 接口交互
- 所有绑定解除:
onUnbind()
→onDestroy()
场景适用:
- 实时交互场景:如音乐控制(播放/暂停/进度)
- 功能模块解耦:如支付模块服务
- 跨进程通信(IPC):不同应用间的数据交换
实战代码:
// 绑定音乐服务
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
MusicService.MusicBinder musicBinder = (MusicService.MusicBinder) binder;
musicBinder.play(); // 直接调用服务方法
}
};
bindService(new Intent(this, MusicService.class), conn, BIND_AUTO_CREATE);
// 解绑服务
unbindService(conn);
Service使用场景
音乐/视频播放服务
核心实现方案:
public class MediaPlaybackService extends Service
implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener {
private MediaPlayer mediaPlayer;
private MediaSession mediaSession;
private static final int NOTIFICATION_ID = 101;
@Override
public void onCreate() {
// 初始化媒体播放器
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build());
// 创建媒体会话(支持锁屏控制)
mediaSession = new MediaSession(this, "MediaPlaybackService");
mediaSession.setCallback(new MediaSessionCallback());
mediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
// 设置通知通道(Android 8.0+必须)
createNotificationChannel();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent.getStringExtra("action");
if("PLAY".equals(action)) {
String mediaUrl = intent.getStringExtra("media_url");
playMedia(mediaUrl);
}
else if("PAUSE".equals(action)) {
pausePlayback();
}
return START_NOT_STICKY;
}
private void playMedia(String mediaUrl) {
try {
// 停止当前播放
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
}
mediaPlayer.reset();
mediaPlayer.setDataSource(mediaUrl);
mediaPlayer.prepareAsync(); // 异步准备播放
mediaPlayer.setOnPreparedListener(this);
// 转换为前台服务
Notification notification = buildMediaNotification("正在播放");
startForeground(NOTIFICATION_ID, notification);
} catch (IOException e) {
Log.e("MediaService", "播放初始化失败", e);
}
}
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
mediaSession.setActive(true);
// 更新通知显示播放状态
Notification notification = buildMediaNotification("正在播放");
NotificationManager nm = getSystemService(NotificationManager.class);
nm.notify(NOTIFICATION_ID, notification);
}
// 构建媒体通知(含播放控制按钮)
private Notification buildMediaNotification(String status) {
// 构建通知内容(包含播放控制按钮)
// ...
}
}
关键技术与注意事项:
- 音频焦点管理:
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(
focusChangeListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- 耳机事件处理:
// 注册耳机拔出广播
IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
registerReceiver(noisyReceiver, intentFilter);
BroadcastReceiver noisyReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if(AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
pausePlayback(); // 耳机拔出时暂停播放
}
}
};
- 通知栏控制:
- 通过 MediaStyle 通知样式添加播放控制按钮
- 更新播放进度(Android 12+支持直接显示播放进度条)
- 播放状态持久化:
- 在 onDestroy() 中保存当前播放位置
- 在 onCreate() 中恢复最后播放位置