3. 启动前台服务:Service.startForeground()方法、通知构建与权限要求
说实话,写一个后台服务不难,但把它变成前台服务——嗯,这里面的坑比想象中多。我刚入行那会儿,以为前台服务不就是调一下 startForeground() 嘛?结果第一次上线的 App 在 Android 12 上直接闪退……后来才明白,通知不行、权限不对,系统真会直接拿你开刀。
这一节咱们就掰开揉碎讲清楚:怎么启动一个合法的前台服务,通知怎么构建才不会被系统扇耳光,以及各种 Android 版本下的权限要求。你想想看,如果你的服务连前台都算不上,那后面监控 CPU 帧率压根就没法聊。
3.1 startForeground() 的调用时机与签名
先看方法签名,很简单:
public final void startForeground(int id, Notification notification)
两个参数:
- id:通知的唯一标识,不能为 0。我习惯用
System.currentTimeMillis()转 int,或者直接固定一个常量如1001。 - notification:一个构造好的
Notification对象,必须包含渠道(Android 8.0+ 强制)。
onStartCommand() 或 onCreate() 中调用,而且必须在服务启动后的几秒内完成。如果超时未调用,系统会认为你的服务是“非法前台服务”,直接杀掉——我在小米手机上遇到过,血泪教训。
3.2 通知渠道:Android 8.0 的强制门槛
Android 8.0(API 26)开始,所有通知都必须绑定一个通知渠道(NotificationChannel)。前台服务通知也不例外。没渠道?通知发不出来,startForeground() 直接抛异常。
我个人习惯在 Application 的 onCreate() 里提前创建好渠道:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID_FOREGROUND,
"前台服务通知",
NotificationManager.IMPORTANCE_LOW // 前台服务通知建议用 LOW,不打扰用户
).apply {
description = "用于显示前台服务的持续运行状态"
}
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
}
注意优先级选 IMPORTANCE_LOW 或 IMPORTANCE_MIN——前台服务是常驻通知,闹钟似的高优先级会让用户暴躁。我曾经有个项目用了 IMPORTANCE_HIGH,结果被用户骂到改版。
3.3 通知构建:一个“合格”的前台服务通知长什么样?
光有渠道还不够,通知本身也得有“身份感”。Android 13 之后系统会更严格检查通知的内容和样式。我总结了一个“三件套”最低要求:
- 标题和正文:不能为空。哪怕写“正在运行”也行,但不能空着。
- 小图标:自定义一个白色图标的
drawable,不要用系统默认的。 - 点击行为:最好有个
PendingIntent跳回应用主页,方便用户关闭服务。
代码示例:
val notification = NotificationCompat.Builder(this, CHANNEL_ID_FOREGROUND)
.setContentTitle("CPU监控服务")
.setContentText("正在采集帧率数据...")
.setSmallIcon(R.drawable.ic_monitor)
.setContentIntent(pendingIntent) // 点击回到 Activity
.setOngoing(true) // 用户不能侧滑清除
.setSilent(true) // 不播放声音
.build()
startForeground(NOTIFICATION_ID, notification)
setOngoing(true) 是前台服务通知的标配。如果你忘了设,用户长按“停止”后服务可能会被杀——我亲测过,真的会发生。
3.4 权限要求:从 Android 9 到 Android 14 的进化史
前台服务权限不是一直都有,而是 Android 9(API 28)之后逐步收紧的。我整理了一张表,你对照项目里的 minSdkVersion 检查:
| Android 版本 | 所需权限 | 备注 |
|---|---|---|
| 8.0 - 8.1(API 26-27) | 无特殊前台权限,但需通知渠道 | 直接 startForeground() 即可 |
| 9.0(API 28) | FOREGROUND_SERVICE | 必须声明,否则运行时崩溃 |
| 10.0 - 12.0(API 29-31) | FOREGROUND_SERVICE + 部分类型需要额外权限 | 如 location 等特殊类型需动态申请 |
| 13.0(API 33) | FOREGROUND_SERVICE + POST_NOTIFICATIONS | 通知权限必须动态弹窗申请 |
| 14.0(API 34) | 同上+FOREGROUND_SERVICE_TYPE_* 必须指定 | 前台服务类型必须在 manifest 中列出 |
看看,是不是越来越复杂?我遇到过最坑的就是 Android 13 上忘记加 POST_NOTIFICATIONS 权限,结果 startForeground() 直接抛 SecurityException,而且日志里只有一行“Permission denied”……排查了两小时才找到原因。
3.5 前台服务类型:Android 14 必须明确声明
从 Android 12 开始,Google 要求前台服务必须在 Manifest 中指定 android:foregroundServiceType。Android 14 更严格——类型不写,startForeground() 直接失败。
我们的 CPU 帧率监控服务,本质上属于“数据处理”或“特殊用途”。但注意,没有直接的“CPU监控”类型。我一般用 dataSync 或 shortService,但更合理的做法是声明 specialUse 并配上说明:
<service
android:name=".ForegroundMonitorService"
android:foregroundServiceType="dataSync|specialUse"
android:exported="false" />
| 分隔。但注意,specialUse 需要提交 Google 审核才能上架——所以一般项目里用 dataSync 就够了,除非你的监控确实是“特殊用途”。
3.6 完整启动流程:一张图说清
下面这张图把整个前台服务启动的步骤串起来了。从创建通知渠道到调用 startForeground(),每一步出错都可能崩。
你看,在绿色节点调用之前,有四个前置检查。任何一个漏掉,你的服务就启动不了。这就是为什么很多新手觉得“我就是调了个 startForeground 怎么报错了”——其实前面缺了一堆配置。
3.7 常见坑与避坑指南
这里列几个我真实踩过的坑:
- 坑1:在服务还没绑定通知渠道时就调用 startForeground()——Android 8.0+ 会直接崩溃。解决办法是在 Application 初始化时就创建渠道。
- 坑2:前台服务通知的 SmallIcon 用了彩色图——Android 系统会自动把彩色图标压成白色,但有些 ROM 会显示异常。我建议统一用白色透明底。
- 坑3:Android 13+ 忘记请求 POST_NOTIFICATIONS 权限——这个权限是运行时权限,需要在
startForeground()之前弹窗请求。我曾经在一个库项目里忘了加,测试手机直接闪退,日志一时半会没看出来。 - 坑4:多个前台服务共用同一个通知 ID——如果你不传入唯一 ID,后启动的服务会覆盖前一个通知,导致系统误以为服务停止。我习惯用随机数生成 ID。
startForegroundService(),在里面统一处理权限检查、渠道创建、通知构建。这样所有前台服务都走同一套逻辑,不容易遗漏。我就是这么干的。
3.8 小结这节课的核心
启动前台服务不是一行代码的事。你得准备:
- 通知渠道(API 26+)
- 通知对象(必须有标题、图标、点击行为)
- 权限声明(
FOREGROUND_SERVICE是底线,API 33+ 要POST_NOTIFICATIONS) - 前台服务类型(API 34+ 必须写)