第一章 前台服务基础:定义、生命周期、启动方式

大伙儿都知道,Android 里 Service 分两种:后台服务和前台服务。但你真的清楚前台服务到底强在哪?说白了,前台服务就是「戴了个高帽子的服务」——它通过一个持续存在的通知,告诉系统「我很重要,别杀我」。嗯,这个机制在 API 26 以后尤其关键。

一句话总结: 前台服务 = 服务 + 不可移除的通知。优先级高,进程被回收的概率极低。

1.1 前台服务的定义

官方文档说它「用于执行用户可感知的长时间运行操作」。我个人习惯把它理解成:用户肉眼能看到的后台任务。举个例子,音乐播放器、导航 App、运动跟踪——这些场景如果只用普通 Service,系统很可能在你切到后台几分钟后就把它杀了。前台服务就不一样,只要通知还在,系统就认为用户知道你在干活,不会轻易回收。

我记得第一次给项目加前台服务时,同事说「不就是加个通知嘛」。结果踩了坑——我忘了在 Android 12 以上还需要 FOREGROUND_SERVICE_TYPE,导致高版本直接崩溃。当时那个崩溃日志我到现在还记得清清楚楚。

重要: 从 Android 9(API 28)开始,普通应用不能再通过后台启动前台服务。必须通过 Activity.startForegroundService() 或者 AlarmManager 等合法途径。否则你会收到 ForegroundServiceStartNotAllowedException。

1.2 生命周期:比想象中多几步

前台服务的生命周期比普通 Service 多了两个关键节点:创建通知、调用 startForeground()。整体流程如下:

  1. 创建服务实例: 系统调 onCreate()
  2. 处理启动请求: onStartCommand() 被调用,拿到 Intent
  3. 绑定通知: 必须在 onStartCommand() 返回前调用 startForeground(),传递 notification ID 和 Notification 对象
  4. 运行中: 服务保持存活,系统不会轻易杀掉
  5. 解除前台状态: 调用 stopForeground()(可选),或所有工作完成后调用 stopSelf()
  6. 销毁: onDestroy() 清理资源

这里有个容易翻车的地方——从 Android 12(API 31)开始,你必须在 startForeground() 之前声明 android:foregroundServiceType 属性,并且要在 manifest 里配置正确的类型。我去年帮一个同事排查 bug,他死活不记得加这个声明,结果每次在 onStartCommand 里 startForeground() 就 crash。查了半小时才发现。

生命周期时间线(常见场景):

阶段系统行为开发者注意事项
onCreate初始化资源避免耗时操作
onStartCommand接收启动Intent5秒内调用startForeground()
运行中系统维持进程高优先级及时更新通知内容
onDestroy清理回收释放后台线程、Closeable资源

1.3 启动方式:三种姿势,别搞混

启动前台服务主要有三种方式,每种都有它的适用场景。我按 API 级别给你捋一捋:

  • 方式一:startService(API 26 以前)
    老项目常见,直接 startService(intent),然后在 onStartCommand 里调 startForeground()。但现在千万别这么写 —— 高版本会直接抛异常。
  • 方式二:startForegroundService(API 26+ 推荐)
    标准写法:context.startForegroundService(intent)。注意,必须在 onStartCommand 返回前调 startForeground(),不然系统会看它超时,直接 kill 掉服务并报 Context.startForegroundService() did not then call Service.startForeground()。
  • 方式三:通过 JobScheduler / AlarmManager 间接启动
    适用于需要精确定时或者满足某个条件再启动的场景。比如我用 AlarmManager 设置一个 PendingIntent 指向 BroadcastReceiver,在 onReceive 里再调 startForegroundService。但一定要注意:Android 12 以后,AlarmManager 调起的定时任务如果延迟了,前台服务启动时间窗口可能已经过期。

你可能会问:「为什么不能直接在 onCreate 里调 startForeground?」嗯,onCreate 时还没有 Intent,你连 task ID 都拿不到。系统要求在 onStartCommand 收到启动 Intent 后才能绑定通知,这是有道理的——通知内容通常要基于 Intent 传过来的数据。

下面是一个标准的前台服务启动代码模板,我用在了好几个项目里,稳定运行没出过问题:

// 在 Activity 或 Fragment 中启动
Intent intent = new Intent(this, MyForegroundService.class);
intent.putExtra("action", "start");
ContextCompat.startForegroundService(this, intent);

// MyForegroundService.java
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    String action = intent != null ? intent.getStringExtra("action") : "";
    if ("start".equals(action)) {
        // 构建通知(省略具体代码)
        Notification notification = buildNotification();
        // 必须在5秒内调用
        startForeground(NOTIFICATION_ID, notification);
        // 开始实际工作...
        doWork();
    }
    return START_STICKY;  // 或者 START_REDELIVER_INTENT
}
特别注意: 从 Android 13(API 33)起,前台服务权限被拆得更细了。除了 FOREGROUND_SERVICE,你还需要在 manifest 里指定具体的 foregroundServiceType,比如 dataSync、location、mediaPlayback 等等。如果不匹配,startForeground() 会直接挂。这个属于系统级限制,绕不过去,老老实实声明就好。

1.4 知识框架:一张图理清

下面这张图概括了前台服务的核心要素:启动方式、生命周期节点、以及和普通 Service 的区别。我画的时候特意把「通知绑定」这一步标了红色——这是最容易出 bug 的地方。

前台服务知识体系 前台服务 (Foreground Service) startForegroundService() AlarmManager / JobScheduler startService() (旧/不推荐) onCreate() onStartCommand() ⏰ 必须5秒内 startForeground() 运行中 onDestroy() Android 12+ 需声明 type 不可移除的通知 ⚠ 启动后5秒内必须调用 startForeground() ☑ 高版本强制前台服务类型声明

1.5 一些实战经验

我有个项目是做运动追踪的,用前台服务记录 GPS 轨迹。开始没注意,在 Android 12 设备上测试,每次切后台不到一分钟服务就被杀了。找了好久才发现——我没有在 manifest 里声明 android:foregroundServiceType="location"。加上就好了。你想想看,一个前台服务如果连类型都不声明,系统怎么知道你该有什么权限?

另外,关于 START_STICKY 和 START_REDELIVER_INTENT 的选择,我个人习惯:如果服务是「轮训」式的(比如每30秒检查一次服务器),用 START_STICKY,被杀后会重启但不重传 Intent;如果是「一次性任务」且有重要数据(比如上传文件),用 START_REDELIVER_INTENT,被杀后系统会重新给你那个 Intent。这个选择会影响前台服务异常后的行为,我曾经因为用了 START_NOT_STICKY,结果用户刚打开 App 就切后台,服务直接不跑了。

小技巧: 调试前台服务时,可以用 adb 命令 adb shell dumpsys activity services 查看当前所有正在运行的 Service,包括前台状态、进程优先级、是否绑定通知等。我一般在测试 crash 后都会跑一下这个命令,看看服务是不是还活着。