当应用程序组件(如 Activity
)通过调用 startService()
方法启动一个服务时,该服务即进入“启动”状态。
核心思想:一旦启动,服务将独立于启动它的组件,无限期地在后台运行,直到服务自身调用
stopSelf()
完成其工作,或者其他组件调用stopService()
将其停止。
服务必须在 AndroidManifest.xml
文件中进行声明。
<manifest ...>
<application ...>
<service
android:name=".MyStartedService"
android:exported="false" />
</application>
</manifest>
启动方式如下:
// Kotlin
val startIntent = Intent(this, MyStartedService::class.java)
startIntent.putExtra("EXTRA_PARAM", "value")
startService(startIntent)
// Java
Intent startIntent = new Intent(this, MyStartedService.class);
startIntent.putExtra("EXTRA_PARAM", "value");
startService(startIntent);
启动的服务的生命周期相对简单,主要涉及以下回调方法:
onCreate()
:首次创建服务时调用。所有仅需执行一次的初始化操作都应在此处完成。onStartCommand(intent: Intent?, flags: Int, startId: Int): Int
:当其他组件通过 startService()
请求启动服务时调用。此方法可以被多次调用。服务在这里执行其实际任务。
START_STICKY
、START_NOT_STICKY
)。onDestroy()
:当服务不再使用且将被销毁时调用。在这里应清理所有资源,如线程、注册的监听器等。启动的服务与启动它的组件之间通常是单向通信。组件通过 Intent
将数据传递给 onStartCommand()
,但服务默认不会将结果直接返回给该组件。
如果需要通信,可以采用 BroadcastReceiver
或 ResultReceiver
等机制。
当应用程序组件通过调用 bindService()
方法连接到一个服务时,该服务即进入“绑定”状态。
核心思想:绑定的服务提供了一个客户端-服务器(Client-Server)接口,允许组件与服务进行交互,例如发送请求、获取结果,甚至通过进程间通信(IPC)进行跨进程交互。服务的生命周期与其绑定的组件相关联。
服务同样需要在 AndroidManifest.xml
中声明。
<manifest ...>
<application ...>
<service
android:name=".MyBoundService"
android:exported="false" />
</application>
</manifest>
绑定过程涉及三个部分:Service
本身、ServiceConnection
回调以及 bindService()
调用。
Service 实现:
// MyBoundService.kt
class MyBoundService : Service() {
private val binder = LocalBinder()
// 客户端可以通过这个 Binder 获取 Service 实例
inner class LocalBinder : Binder() {
fun getService(): MyBoundService = this@MyBoundService
}
override fun onBind(intent: Intent): IBinder {
return binder
}
// 供客户端调用的公共方法
fun getRandomNumber(): Int {
return (0..100).random()
}
}
客户端绑定:
// 在 Activity 中
private var myBoundService: MyBoundService? = null
private var isBound = false
// 定义 ServiceConnection 回调
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// 连接成功时回调,获取 Service 实例
val binder = service as MyBoundService.LocalBinder
myBoundService = binder.getService()
isBound = true
// 此处可以开始调用 Service 的公共方法
}
override fun onServiceDisconnected(arg0: ComponentName) {
// 当服务意外断开(如崩溃或被系统杀死)时回调
isBound = false
}
}
override fun onStart() {
super.onStart()
// 绑定服务
Intent(this, MyBoundService::class.java).also { intent ->
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
}
override fun onStop() {
super.onStop()
// 解除绑定,防止内存泄漏
if (isBound) {
unbindService(connection)
isBound = false
}
}
绑定的服务的生命周期依赖于所有绑定到它的客户端:
onCreate()
:当第一个客户端绑定到服务时,系统会创建服务并调用此方法。onBind(intent: Intent?): IBinder?
:当一个客户端通过 bindService()
绑定到服务时调用。此方法必须返回一个 IBinder
接口,客户端通过它与服务通信。onUnbind(intent: Intent?): Boolean
:当所有客户端都通过 unbindService()
解除绑定时调用。如果此方法返回 true
,则下次有新客户端绑定时,系统将调用 onRebind()
而不是 onBind()
。onDestroy()
:当所有绑定的客户端都已解除绑定后,系统会销毁该服务并调用此方法。为了更直观地对比,下表总结了两种服务模式的核心区别:
特性 | 启动的服务 (Started Service) | 绑定的服务 (Bound Service) |
---|---|---|
启动方式 | startService() |
bindService() |
生命周期 | 独立于启动者,需手动停止 (stopSelf /stopService ) |
与所有绑定的客户端相关联,当所有客户端都解绑后自动销毁 |
与组件关系 | “启动后无关”模式,服务独立运行 | 客户端-服务器(C/S)模式,服务为客户端提供接口 |
通信方式 | 通常是单向(Intent ),或借助 BroadcastReceiver |
双向,通过 IBinder 接口直接调用服务的方法 |
适用场景 | 执行不需立即返回结果的后台任务,如音乐播放、文件下载、数据同步等 | 为多个组件提供可复用的后台功能,需要与服务进行频繁交互的场景 |
选择哪种服务模式完全取决于你的需求:
startService()
:当你需要一个独立的后台工作者时。例如,一个音乐播放器服务,即使用户离开播放器 Activity
,音乐也应继续播放。bindService()
:当你需要一个为 Activity
或其他组件提供数据或执行方法的后台组件时。例如,一个实时获取地理位置的服务,Activity
需要频繁查询最新的位置信息。startService()
启动以确保能长期在后台播放,同时它也允许 Activity
通过 bindService()
连接上来,以控制播放(暂停/下一首)和获取当前播放信息。在这种情况下,服务只有在同时满足“被停止”(stopSelf
/stopService
)和“所有客户端都解绑”这两个条件时,才会被销毁。Service
的所有回调方法(onCreate()
, onStartCommand()
, onBind()
等)都运行在应用程序的主线程(UI 线程)上。因此,任何耗时操作(如网络请求、文件 I/O)都必须在服务内部的后台线程中执行,以避免 ANR(Application Not Responding)。推荐使用 Kotlin Coroutines 或 Java 的 ExecutorService
。startForeground()
显示一个持续的通知。bindService()
时,务必在组件的相应生命周期方法(如 onStop()
或 onDestroy()
)中调用 unbindService()
。否则,会造成 ServiceConnection
泄漏,导致服务无法正常销毁。IntentService
(已弃用):IntentService
是 Service
的一个子类,它会自动创建工作线程来处理 onHandleIntent()
中的所有请求。虽然方便,但自 Android 11 (API 30) 起已被弃用,官方推荐使用 WorkManager
或结合 Coroutine
的 Service
作为替代方案。