本文中“Service”与“服务”所指代的是同一概念。
Service的定义及作用
作为四大组件之一,Service在Android系统中主要用来在后台执行那些需要长期运行的、没有界面的任务。适合以下场景:
- 耗时任务,如文件下载上传、数据解析、本地运行算法操作图片等。
- 实现持续运行功能,如音乐播放、实时定位、运动时的节拍器(本质上也是音乐播放)等。
- 支持对外开放组件,如响应其它组件(包括远程)的请求。
为了支持其功能,Service具有以下特性:
- 无界面:没有UI,用户唯一能看到的是系统通知。
- 后台运行:可以在所有Activity关闭后继续在后台运行。
- 并发执行:支持异步任务以及多线程。
Service的生命周期
Service具有两种不同的启动方式,这两种启动方式下,它的生命周期也是不同的。
通过startService启动
- Service独立运行,与Context生命周期无关,即使启动Service的组件销毁,Service仍然继续运行。
- 需要显示调用stopService或者stopSelf来停止Service。
通过startService启动后的生命周期
onCreate():服务被首次创建时调用。onStartCommand():每次通过startService启动服务时调用。onDestroy():服务被停止时调用。
通过bindService启动
- 服务与绑定它的组件生命周期相关联。当所有绑定的组件都解绑时,服务会销毁。
- 调用者(客户端)通过
onBind()返回的 IBinder 与服务通信。
通过bindService启动后的生命周期
onCreate():服务被首次创建时调用。onBind():组件绑定到服务时调用,返回一个 IBinder 接口。onUnbind():最后一个绑定的组件解绑时调用。onDestroy():服务被销毁时调用。
Service的主要类型
从Service运行时的优先级、是否允许被回收、是否有通知、是否与客户端(调用者)有交互这几个角度,可以将Service分为普通(后台)、高优先级(前台)、绑定这几种类型。
此外,还有几种特殊的服务,可用于异步、延迟、适配电池策略等场景。
普通服务(后台服务)
- 通过
startService启动。 - 独立运行,需要显式调用
stopService来停止。 - 执行轻量、短期的任务,如下载文件、上传数据。
- 优先级低,在系统资源紧张时会被系统回收。
class MyService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// 执行任务
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
高优先级服务(前台服务)
- 通过
startForeground()启动,启动时需要传入一个Notification对象。 - 不容易被系统杀死,适用于重要任务。
- 适用于需要持续运行的任务,如音乐播放、持续定位。
- 优先级高,运行时占用系统资源,需要谨慎使用,避免对内存和电池造成负担。
class MyForegroundService : Service() {
override fun onCreate() {
val notification = NotificationCompat.Builder(this, "channelId")
.setContentTitle("Service Running")
.setSmallIcon(R.drawable.ic_notification)
.build()
startForeground(1, notification)
}
}
绑定服务
- 通过
bindService启动,允许客户端与Service之间双向通信。 - 生命周期与客户端相关。
- 适用于需要交互的功能,如音乐播放、TTS。
class MyBoundService : Service() {
private val binder = LocalBinder()
inner class LocalBinder : Binder() {
fun getService(): MyBoundService = this@MyBoundService
}
override fun onBind(intent: Intent?): IBinder {
return binder
}
}
特殊服务
JobIntentService
用于处理异步任务,自动适配不同 Android 版本的后台策略。
JobScheduler
用于延迟执行任务或满足特定条件时执行。
WorkManager
推荐用于替代后台任务,支持约束条件和持久性。
常见问题与解决方法
前台Service通知无法关闭
前台Service要求必须有与之对应的通知(Notification),如果通知被移除,会导致系统停止服务。
对 Android 8.0 以下版本,可以利用系统漏洞隐藏通知,但这并不推荐,可能会带来不稳定性。
Android 12+ 提供了 NotificationManager.IMPORTANCE_MIN,可以进一步降低通知可见性。
从用户体验的角度考虑,建议提供“关闭通知”的选项,但关闭通知时需要告知用户,服务可能被停止。
后台Service被系统杀死
Android 系统从 Android 8.0 开始加强了后台服务的限制,禁止普通后台服务长期运行。后台服务运行时可能因资源不足、权限问题或电池优化而被系统杀死。
Android 官方推荐使用 WorkManager 处理后台任务,它能适配电池优化和后台限制。
适配Doze模式
Doze 模式 是从 Android 6.0 (API 23) 开始引入的一种电池优化机制,旨在减少设备在不活动时的电量消耗。
Doze 模式的工作原理
当设备处于非活跃状态时(例如屏幕关闭且设备未被使用),系统会进入 Doze 模式,限制某些后台活动以节省电量。
Doze 模式的触发条件
- 设备处于静止状态(没有明显的移动或操作)。
- 屏幕关闭且未插入电源。
- 没有运行高优先级的前台任务。
Doze 模式的限制
在 Doze 模式下,系统会限制以下活动:
- 网络访问:应用无法自由地访问网络。
- 后台任务调度:后台服务(例如定时任务或轮询任务)受到严格限制。
- AlarmManager:普通闹钟不会触发,只有高优先级任务(如
setExactAndAllowWhileIdle())可以运行。- 同步和作业调度:JobScheduler 和 SyncAdapter 的任务可能被推迟。
对需要长时间运行的任务,需设置 setExactAndAllowWhileIdle() 以确保任务在 Doze 模式下触发。
WorkManager与AlarmManager
| 特性 | WorkManager | AlarmManager |
|---|---|---|
| 任务类型 | 适用于需要灵活调度的后台任务,允许延迟或条件触发。 | 适用于需要精确时间触发的任务,无论设备是否处于活动状态。 |
| 系统支持 | 自动适配 Android 系统优化机制(如 Doze 模式、App Standby)。 | 在 Doze 模式下,普通闹钟任务会被延迟,除非使用 setExactAndAllowWhileIdle()。 |
| 任务持久化 | 支持任务持久化,任务配置存储在数据库中,即使设备重启或应用被杀死也能恢复。 | 默认不支持任务持久化,设备重启后需重新设置任务。 |
| 任务约束 | 支持多种条件(如网络连接、电池状态、存储空间等),任务在条件满足时执行。 | 不支持复杂约束,只能根据时间触发。 |
| 使用复杂度 | 更高级,适合处理复杂任务调度逻辑。 | 更简单,适合处理精确时间任务。 |
| 触发频率限制 | 周期性任务的最小间隔为 15 分钟(API 限制)。 | 无间隔限制,可以高频率触发任务。 |
选择 WorkManager 的场景
- 任务不要求精确触发时间。
- 需要任务持久化和条件约束(如网络状态、电池电量)。
- 任务与 Android 高版本优化机制(如 Doze 模式)兼容性强。
选择 AlarmManager 的场景
- 需要精确触发时间。
- 任务不依赖复杂条件。
- 对电池优化模式有更高控制要求。