Android Service 完全入门指南
1. 什么是 Service?
Service(服务)是 Android 四大组件之一,专门用来在后台执行长时间运行的操作 ,它不提供用户界面。比如:即使你切换到其他 App,音乐播放器仍能继续放歌;或者你在后台下载文件,用户仍可操作其他界面。
关键点:
-
Service 运行在主线程 (UI 线程)上,默认不是在独立线程或进程中。
-
如果要在 Service 里做耗时任务(网络请求、文件读写),必须自行开启线程,否则会 ANR(应用无响应)。
-
Service 的优先级比后台 Activity 高,系统更不容易杀掉它。
2. Service 的三种类型
根据使用方式和可见性,Service 主要分为三类:
| 类型 | 说明 | 典型应用 |
|---|---|---|
| 前台服务 (Foreground Service) | 会显示一个不可消除的通知,用户明确感知到它在运行。优先级高,几乎不会被系统杀死。 | 音乐播放、导航、运动追踪 |
| 后台服务 (Background Service) | 不显示通知,用户可能不知道它在运行。在 Android 8.0 后受到严格限制。 | 数据同步、静默清理缓存(已逐渐被 WorkManager 取代) |
| 绑定服务 (Bound Service) | 允许其他组件(如 Activity)通过绑定方式与其交互。当所有组件解绑后,服务销毁。 | Activity 与服务通信,如获取播放进度、控制播放 |
一个 Service 可以同时是"启动服务"和"绑定服务",既可被启动,也可被绑定。
3. Service 的生命周期
理解生命周期是正确使用 Service 的关键。取决于你如何启动它:
3.1 启动服务 (Started Service)
通过 startService() 启动,拥有自己的生命周期,与启动者无关。
text
onCreate() → onStartCommand() → Service running → onDestroy()
-
onCreate():服务首次创建时调用,做一次初始化(只执行一次)。
-
onStartCommand() :每次
startService()都会触发,你可以在此处理Intent传入的数据。 -
onDestroy():服务销毁前调用,做清理工作(释放线程、取消监听等)。
这种服务必须通过 stopSelf() 或 stopService() 来停止。
3.2 绑定服务 (Bound Service)
通过 bindService() 绑定,生命周期与客户端绑定。
text
onCreate() → onBind() → Clients are bound → onUnbind() → onDestroy()
-
onCreate():初始化。
-
onBind() :返回一个
IBinder,客户端通过它与服务交互。 -
onUnbind():所有客户端解绑时调用。
-
onDestroy():服务销毁。
若同时 start 和 bind 了一个服务,则必须取消绑定并调用
stopService()/stopSelf()才会销毁。
4. 声明与权限
所有 Service 必须在 AndroidManifest.xml 中注册:
xml
<service
android:name=".MyService"
android:enabled="true"
android:exported="false" />
-
exported:是否允许其他应用访问。通常设为false。 -
前台服务还必须声明权限(Android 9+):
xml
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
从 Android 14 (API 34) 开始,还需要指定前台服务类型:
xml
<service
android:name=".MyForegroundService"
android:foregroundServiceType="location" />
以及对应的权限,如 ACCESS_FINE_LOCATION。
5. 创建并使用不同类型 Service
5.1 启动服务(后台服务)
步骤 1:创建类继承 Service
kotlin
class MyBackgroundService : Service() {
override fun onCreate() {
super.onCreate()
// 初始化工作
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// 在后台线程执行任务
Thread {
// 模拟耗时操作
Thread.sleep(3000)
// 任务完成后记得停止服务
stopSelf()
}.start()
// 如果系统因内存不足杀掉服务,让系统在内存允许时重建服务并重新调用 onStartCommand
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
}
返回值说明(onStartCommand 返回值):
-
START_STICKY:服务被杀死后系统会尝试重新创建,但 Intent 为 null,适合播放音乐等。 -
START_NOT_STICKY:被杀死后不重新创建,除非有未处理的 Intent。 -
START_REDELIVER_INTENT:重新创建且重新传递之前的 Intent,适合下载类任务。
步骤 2:在 Activity 中启动/停止
kotlin
val intent = Intent(this, MyBackgroundService::class.java)
startService(intent) // 启动
// 停止服务
stopService(intent)
从 Android 8.0(API 26)开始,后台服务启动有严格限制:如果应用不在前台,调用
startService()会抛异常。此时应使用startForegroundService()并迅速升级为前台服务,或者改用 WorkManager/JobScheduler。
5.2 前台服务
前台服务必须有一个持续存在的通知,告知用户服务正在运行。
步骤 1:创建通知渠道(Android 8.0+ 必须)
kotlin
private fun createNotificationChannel() {
val channel = NotificationChannel(
"my_channel_id",
"前台服务通知",
NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
步骤 2:构建通知
kotlin
val notification = NotificationCompat.Builder(this, "my_channel_id")
.setContentTitle("正在播放音乐")
.setContentText("歌曲名:晴天")
.setSmallIcon(R.drawable.ic_music)
.build()
步骤 3:在前台服务中启动
kotlin
class MyForegroundService : Service() {
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(1, notification) // 1是通知ID,唯一即可
// 执行任务...
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
// 可调用 stopForeground(true) 移除通知(true表示移除通知)
}
override fun onBind(intent: Intent?): IBinder? = null
}
启动前台服务(Android 8.0+必须用 startForegroundService):
kotlin
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
停止前台服务:
kotlin
stopService(intent)
5.3 绑定服务
当你需要让 Activity 与服务直接通信(调用方法、获取数据)时使用。
步骤 1:创建服务,返回 Binder
kotlin
class MyBindService : Service() {
// 1. 创建内部Binder类
inner class LocalBinder : Binder() {
fun getService(): MyBindService = this@MyBindService
}
private val binder = LocalBinder()
// 2. onBind 返回 binder
override fun onBind(intent: Intent?): IBinder = binder
// 3. 提供给客户端的公共方法
fun getCurrentPosition(): Int = 12345
}
步骤 2:在 Activity 中绑定并连接
kotlin
class MainActivity : AppCompatActivity() {
private var boundService: MyBindService? = null
private var isBound = false
// 连接管理
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as MyBindService.LocalBinder
boundService = binder.getService()
isBound = true
}
override fun onServiceDisconnected(name: ComponentName?) {
isBound = false
boundService = null
}
}
override fun onStart() {
super.onStart()
val intent = Intent(this, MyBindService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
override fun onStop() {
super.onStop()
if (isBound) {
unbindService(connection)
isBound = false
}
}
fun useService() {
// 调用服务方法
val pos = boundService?.getCurrentPosition()
}
}
6. 别再用 IntentService 了
IntentService 是 Service 子类,自动在工作线程处理队列化的 Intent,处理完自动停止。但它在 Android 11 (API 30) 已被弃用,因为缺少对后台启动限制的适配,且不够灵活。
替代方案:
-
WorkManager:适合可延迟、但必须执行的后台任务(上传日志、定期同步)。
-
JobIntentService(已废弃,建议 WorkManager)。
-
手动创建 Service +
Thread/Coroutine。
简而言之,初学者可直接用 Service + Kotlin 协程,或使用 WorkManager。
7. Service 与 WorkManager 的选择
| 场景 | 推荐方案 |
|---|---|
| 必须有通知,用户能感知,不允许被杀死 | 前台服务 |
| 与当前界面交互,获取数据/控制 | 绑定服务 |
| 后台任务,可延迟,需保证执行(即使应用退出) | WorkManager |
| 一次性简单后台任务,应用在前台即可 | 简单协程 / 在 Activity 里执行 |
WorkManager 使用示例(对比 Service):
kotlin
val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build()
WorkManager.getInstance(context).enqueue(uploadWorkRequest)
8. 常见问题与注意事项
8.1 主线程与 ANR
Service 的 onStartCommand()、onBind() 等生命周期方法都运行在主线程。必须将耗时操作放到子线程(使用 Thread、ExecutorService、Kotlin Coroutines 等)。
8.2 Android 后台执行限制
-
Android 8.0+:不能随意后台启动服务,需改为前台服务。
-
Android 12+ :后台启动的前台服务会有延迟限制且需要指定
foregroundServiceType。 -
当应用从 Recent 中滑掉,后台服务可能被杀死。
最佳实践:如非必要,避免使用长时间后台服务,考虑 WorkManager + 前台服务通知配合。
8.3 内存泄漏
绑定服务一定要在合适的生命周期(如 onStop() 或 onDestroy())里解绑,否则会泄漏 Activity。
8.4 前台服务类型(必需,Android 14+)
从 Android 14 开始,必须在清单中声明 foregroundServiceType,如 location、mediaPlayback 等,并请求对应权限。
xml
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
...
<service android:name=".MyService"
android:foregroundServiceType="location"
android:exported="false"/>
9. 完整示例:简易音乐播放器服务
结合前台服务 + 绑定服务的设计,提供播放控制和进度获取。
MusicService.kt
kotlin
class MusicService : Service() {
private val binder = LocalBinder()
private var isPlaying = false
inner class LocalBinder : Binder() {
fun getService(): MusicService = this@MusicService
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(NOTIFICATION_ID, buildNotification())
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder = binder
fun play() {
isPlaying = true
// 媒体播放逻辑
}
fun pause() {
isPlaying = false
}
fun isPlaying(): Boolean = isPlaying
private fun buildNotification(): Notification {
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Music Player")
.setContentText(if (isPlaying) "Playing..." else "Paused")
.setSmallIcon(R.drawable.ic_music)
.build()
}
companion object {
const val CHANNEL_ID = "music_channel"
const val NOTIFICATION_ID = 101
}
private fun createNotificationChannel() {
val channel = NotificationChannel(
CHANNEL_ID,
"Music",
NotificationManager.IMPORTANCE_LOW
)
getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
}
override fun onDestroy() {
super.onDestroy()
// 释放资源
}
}
在 Activity 中使用
kotlin
class MusicActivity : AppCompatActivity() {
private var musicService: MusicService? = null
private var isBound = false
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as MusicService.LocalBinder
musicService = binder.getService()
isBound = true
}
override fun onServiceDisconnected(name: ComponentName?) {
isBound = false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_music)
val intent = Intent(this, MusicService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
bindService(intent, connection, BIND_AUTO_CREATE)
}
fun onPlayClick(view: View) {
musicService?.play()
}
override fun onDestroy() {
super.onDestroy()
if (isBound) {
unbindService(connection)
isBound = false
}
}
}
10. 总结
-
Service 是 Android 后台能力的核心,但权限和限制已逐年收紧。
-
新手上路建议:先学会 前台服务 + 绑定通信,理解生命周期后再深入 WorkManager。
-
遵守 Android 后台执行规则,保证应用的流畅和用户的电池体验。
推荐延伸阅读: