Android Service 基础:生命周期与类型归纳

刚入行那会儿,我以为 Service 就是后台的 Activity,随便 start 一下就能跑。直到线上漏了一个未绑定的 Service,内存泄漏把用户手机搞到重启……嗯,从那以后我彻底把 Service 的生命周期刻进了脑子里。

这一章咱们把基础打牢。Service 说白了就是一段在后台运行的代码,没有界面。但怎么启动、怎么销毁、有哪些变种——这些搞不清楚,后面的 Foreground Service Mock 回放根本没法做。

核心要点:Service 的生命周期决定资源管理,类型选择影响 Mock 策略。记住这三个分类:Started、Bound、Foreground。

Android Service 生命周期与类型总览 Started Service Bound Service Foreground Service onCreate() onStartCommand() Running(持续后台) onDestroy() onCreate() onBind() → 返回 IBinder Bound(客户端绑定中) unbind onUnbind() → onDestroy() onCreate() startForeground() + 通知 Foreground(高优先级) stopForeground() → onDestroy() 关键区别: • Started:独立存在,停用需 stopService() / stopSelf() • Bound:随客户端绑定/解绑而消亡,不支持 startCommand 返回值 生命周期回调顺序(以普通 Started 为例): onCreate() → onStartCommand(Intent, flags, startId) → onLowMemory()? → onDestroy() 注意:onStartCommand 可能被多次调用,但 onCreate 只执行一次。

一、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 的混合体。它的生命周期就是:

  1. onCreate → 初始化 Mock 引擎
  2. onStartCommand → 启动回放线程,开始读取 Mock 数据
  3. startForeground → 显示通知,提升优先级
  4. 任务完成 → stopForeground + stopSelf
  5. onDestroy → 清理线程池、数据库连接等

这里有个陷阱:如果 Mock 数据量很大,onStartCommand 里直接做耗时操作会阻塞主线程。我建议立即启动一个子线程,或者用协程的 Dispatchers.IO。

我的经验:在 onStartCommand 里不要做超过 100ms 的操作。如果要做初始化,可以 post 到 HandlerThread 或者使用线程池。

嗯,基础就这么多。记住了生命周期,后面的 Mock 实现才不会出岔子。