前台服务
本教程共 100 篇 · 第 33 篇 · 更新于 2026-07-28 · 约 9 分钟阅读
33. 前台服务
本节目标:理解前台服务是什么、为什么需要它、怎么用 startForeground 绑定通知、前台服务类型怎么选、Android 14+ 的新要求和后台限制。
什么是前台服务
前台服务(Foreground Service)是一种特殊的服务,它显示一个持续通知告诉用户「我正在后台干活」。因为有这个可见通知,系统把它当高优先级,不会轻易杀掉。
典型场景:
- 音乐播放器后台播放。
- 导航应用后台持续定位。
- 录屏、录音。
- 文件下载(大文件、长时间)。
- 运动追踪。
Note
- 前台服务和普通服务的区别:前台服务有持续通知,系统认为它对用户可见、重要,优先级高(接近前台进程),不会被内存回收杀掉。普通后台服务随时可能被杀。
为什么需要前台服务
Android 8.0(API 26)起,应用在后台时不能启动普通 Service(startService 抛异常)。但用户切到后台时确实需要继续干活(如听歌),怎么办?
答案是 startForegroundService() 启动一个前台服务,并在 5 秒内调 startForeground() 显示通知。这样系统允许它继续运行。
Warning
- 5 秒内必须调
startForeground(),否则系统抛ForegroundServiceDidNotStartInTimeException,应用崩溃。这是新手常踩的坑。
基本用法
1. 声明权限和服务类型
Manifest:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<!-- Android 14+ 还要声明对应类型的权限 -->
<application ...>
<service
android:name=".MusicService"
android:exported="false"
android:foregroundServiceType="mediaPlayback" />
</application>
Warning
- Android 14(API 34)起,
foregroundServiceType是强制的。每个前台服务必须声明类型,且要声明对应的FOREGROUND_SERVICE_XXX权限。不声明直接崩。
2. 创建通知渠道
Android 8.0+ 通知必须建 Channel:
class MusicService : Service() {
companion object {
const val CHANNEL_ID = "music_playback"
const val NOTIFICATION_ID = 1
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"音乐播放",
NotificationManager.IMPORTANCE_LOW // 播放服务用低重要性,不响铃
).apply {
description = "显示正在播放的音乐"
}
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
}
}
3. 构建通知并 startForeground
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("正在播放")
.setContentText("歌曲名 - 歌手")
.setSmallIcon(R.drawable.ic_music_note) // 必须有
.setOngoing(true) // 不可滑动清除
.setContentIntent(pendingIntent) // 点击通知回到应用
.build()
// Android 14+ 要传类型
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)
} else {
startForeground(NOTIFICATION_ID, notification)
}
// 开始实际工作
startPlayback()
return START_STICKY
}
4. 启动服务
val intent = Intent(context, MusicService::class.java)
ContextCompat.startForegroundService(context, intent)
startForegroundService 会启动服务并调用 onStartCommand,你在里面调 startForeground 转前台。
5. 停止前台服务
// Service 内部干完活
stopForeground(STOP_FOREGROUND_REMOVE) // 移除通知
stopSelf() // 停止服务
// 或外部停止
context.stopService(Intent(context, MusicService::class.java))
前台服务类型
Android 14 起强制声明类型,常见类型:
| 类型 | 用途 | 需要的额外权限 |
|---|---|---|
mediaPlayback | 音频/视频播放 | FOREGROUND_SERVICE_MEDIA_PLAYBACK |
mediaProjection | 录屏 | FOREGROUND_SERVICE_MEDIA_PROJECTION |
camera | 相机(如视频通话) | FOREGROUND_SERVICE_CAMERA + CAMERA |
microphone | 录音 | FOREGROUND_SERVICE_MICROPHONE + RECORD_AUDIO |
location | 持续定位(导航) | FOREGROUND_SERVICE_LOCATION + 定位权限 |
dataSync | 数据同步、上传下载 | FOREGROUND_SERVICE_DATA_SYNC |
health | 健康监测 | FOREGROUND_SERVICE_HEALTH |
connectedDevice | 连接外设(蓝牙、USB) | FOREGROUND_SERVICE_CONNECTED_DEVICE |
remoteMessaging | 车机消息转发 | FOREGROUND_SERVICE_REMOTE_MESSAGING |
systemExempted | 系统级豁免 | 特定应用 |
Note
- 类型要跟实际用途匹配。声明
mediaPlayback但实际做下载,审核会拒,运行时也可能被系统杀。 - 一个服务能声明多个类型(用
\|连接),但通常一个就够。
后台启动限制
Android 12(API 31)起,应用在后台时不能启动前台服务,除非满足豁免条件:
- 用户操作触发(如点击通知、点 widget)。
- 高优先级 FCM 消息。
- 系统广播(如
BOOT_COMPLETED)。 - 前台应用启动。
不满足豁免时调用 startForegroundService 会抛 ForegroundServiceStartNotAllowedException。
Tip
- 应用在后台需要立即执行的短任务,用 expedited WorkManager(加急工作),它能短暂运行类似前台服务,不用自己管通知。第 75 章讲。
通知的必要性
前台服务的通知不能被用户滑动清除(setOngoing(true)),除非:
- 服务调
stopForeground/stopSelf。 - 用户在通知设置里关掉。
- 绑定的 Activity 调用
stopService。
通知内容要清晰告诉用户在干什么,否则用户会觉得「这通知怎么删不掉」而反感。
Warning
setSmallIcon必须设,不设通知不显示,startForeground会失败。- Android 13+ 还要
POST_NOTIFICATIONS运行时权限,用户拒绝也能跑前台服务,但通知不显示(用户体验差)。
实战:音乐播放前台服务
class MusicService : Service() {
private val binder = LocalBinder()
private var mediaPlayer: MediaPlayer? = null
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
inner class LocalBinder : Binder() {
fun getService() = this@MusicService
}
override fun onCreate() {
super.onCreate()
createChannel()
mediaPlayer = MediaPlayer()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(NOTIFICATION_ID, buildNotification("准备播放"))
// 实际播放逻辑
return START_STICKY
}
fun play(song: Song) {
mediaPlayer?.apply {
reset()
setDataSource(song.path)
prepare()
start()
}
updateNotification("正在播放:${song.title}")
}
private fun updateNotification(text: String) {
val manager = getSystemService(NotificationManager::class.java)
manager.notify(NOTIFICATION_ID, buildNotification(text))
}
private fun buildNotification(text: String): Notification {
val pendingIntent = PendingIntent.getActivity(
this, 0,
Intent(this, MainActivity::class.java),
PendingIntent.FLAG_IMMUTABLE
)
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("音乐播放器")
.setContentText(text)
.setSmallIcon(R.drawable.ic_music_note)
.setOngoing(true)
.setContentIntent(pendingIntent)
.build()
}
override fun onBind(intent: Intent?) = binder
override fun onDestroy() {
mediaPlayer?.release()
serviceScope.cancel()
super.onDestroy()
}
}
何时该用前台服务
| 场景 | 用不用前台服务 |
|---|---|
| 后台播放音乐 | 用(mediaPlayback) |
| 导航持续定位 | 用(location) |
| 录屏、通话 | 用(mediaProjection / camera / microphone) |
| 大文件下载 | 看情况,能用 WorkManager 就用 WorkManager |
| 定时同步 | 不用,用 WorkManager |
| 一次性短任务 | 不用,用协程或 WorkManager |
Tip
- 决策原则:需要持续可见、用户主动开启的后台操作用前台服务;可延迟、不需用户感知的用 WorkManager。
小结
前台服务显示持续通知,系统优先级高不被杀。用 startForegroundService 启动,5 秒内必须 startForeground 显示通知。Android 14+ 强制声明 foregroundServiceType 和对应权限。Android 12+ 后台启动受限。场景:音乐播放、导航、录屏通话。普通后台任务用 WorkManager。下一节学 Service 与进程。