Android Service 基础:生命周期与类型归纳
刚入行那会儿,我以为 Service 就是后台的 Activity,随便 start 一下就能跑。直到线上漏了一个未绑定的 Service,内存泄漏把用户手机搞到重启……嗯,从那以后我彻底把 Service 的生命周期刻进了脑子里。
这一章咱们把基础打牢。Service 说白了就是一段在后台运行的代码,没有界面。但怎么启动、怎么销毁、有哪些变种——这些搞不清楚,后面的 Foreground Service Mock 回放根本没法做。
核心要点:Service 的生命周期决定资源管理,类型选择影响 Mock 策略。记住这三个分类:Started、Bound、Foreground。
一、Service 的两种激活方式
其实 Android 的 Service 启动方式只有两种:startService() 和 bindService()。但很多人会把 Foreground Service 当成第三种,它只是 Started Service 的一个子集,加上了一个永不消失的通知。
我个人习惯这样记:
Start → 自己活,自己死。
Bind → 客户端绑着就活,解绑就死。
两者混用?小心内存泄漏,我踩过这个坑。
1. Started Service
- 通过
startService(Intent)启动 - 可无限在后台运行,直到调用
stopSelf()或stopService() - 即使启动它的组件被销毁,Service 依然存活
小技巧:IntentService(已废弃)把请求排队串行处理,适合一次性任务。现在官方推荐用 WorkManager 或协程,但学 Mock 回放时仍需要理解它的生命周期。
2. Bound Service
- 客户端通过
bindService()绑定,返回IBinder接口 - 当所有客户端都解绑时,系统自动销毁 Service
- 常用于跨进程通信(AIDL)或同一进程内提供数据
警告:千万不要在 onBind() 中做耗时操作!Binder 线程池会阻塞,导致 ANR。我曾经在 onBind 里查数据库,上线半小时 ANR 率飙升到 20%。
二、生命周期方法详解
下表把五个关键回调整理出来了。你想想看,onCreate 只调用一次,onStartCommand 可能多次,onBind 只调一次(但 unbind 后可以重新 bind)。
| 回调方法 | 触发时机 | 调用次数 | 备注 |
|---|---|---|---|
| onCreate() | Service 第一次创建时 | 1次 | 适合初始化 |
| onStartCommand() | 每次 startService 调用时 | 多次 | 返回 START_STICKY / NOT_STICKY / REDELIVER_INTENT |
| onBind() | 第一次 bindService 时 | 1次(重绑定再次调用) | 返回 IBinder |
| onUnbind() | 所有客户端解绑时 | 1次 | 返回 true 表示下次绑定调用 onRebind |
| onDestroy() | Service 销毁前 | 1次 | 清理资源 |
三、onStartCommand 的返回值——决定了被杀的策略
很多人忽略了 onStartCommand 的返回值。说实话,我刚开始也觉得随便返回就行。直到有一次低内存时系统把我的下载 Service 杀掉,还没重新创建,用户投诉说下载一直在中断。
三种返回值对比:
- START_NOT_STICKY:系统杀掉后不会重启。适合一次性的任务(比如定时器已经触发)。
- START_STICKY:系统杀掉后会重新调用 onStartCommand,但 Intent 为 null。适合需要持续运行但不需要恢复命令的服务。
- START_REDELIVER_INTENT:系统杀掉后会重新传递最后一次的 Intent。适合需要重试的任务,比如上传文件。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 我个人习惯在这里用 if-else 判断 action
if (intent != null) {
String action = intent.getAction();
// 处理任务...
}
// 文件上传场景 — 必须重试
return START_REDELIVER_INTENT;
}
实战建议:做 Mock 回放时,我们通常把 Service 设计成 START_NOT_STICKY,因为回放过程不想被系统重启打断。但我们会在代码里做断点续传逻辑。
四、Foreground Service — 特殊的存在
为什么单独说它?因为 Android 8.0 之后,后台 Service 被严格限制。你想想看,如果用户切到后台,Service 过几分钟就被杀掉了,还怎么 Mock 回放?所以才需要 Foreground Service:一个前台通知,告诉系统「这个服务很重要,别杀我」。
启动方式很简单,在 onStartCommand 内调用:
// 构建通知(Android 13+ 需要 POST_NOTIFICATIONS 权限)
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Mock 回放中")
.setContentText("正在模拟数据回放...")
.setSmallIcon(R.drawable.ic_mock)
.build();
startForeground(1001, notification);
停止时调用 stopForeground(REMOVE_NOTIFICATION)。注意:必须在 Service 的 onDestroy 或主动停止前调用,否则通知会一直挂着。
五、生命周期与 Mock 回放的关系
这一章虽然没直接写代码,但你得明白:我们后面要 Mock 的 Foreground Service,本质上是一个 Started + Foreground 的混合体。它的生命周期就是:
- onCreate → 初始化 Mock 引擎
- onStartCommand → 启动回放线程,开始读取 Mock 数据
- startForeground → 显示通知,提升优先级
- 任务完成 → stopForeground + stopSelf
- onDestroy → 清理线程池、数据库连接等
这里有个陷阱:如果 Mock 数据量很大,onStartCommand 里直接做耗时操作会阻塞主线程。我建议立即启动一个子线程,或者用协程的 Dispatchers.IO。
我的经验:在 onStartCommand 里不要做超过 100ms 的操作。如果要做初始化,可以 post 到 HandlerThread 或者使用线程池。
嗯,基础就这么多。记住了生命周期,后面的 Mock 实现才不会出岔子。