04-绑定服务与通信
绑定服务与通信
1. Bound Service 是什么
Bound Service 是一种客户端-服务器模式:
Client(Activity/Fragment/其他组件) 绑定 Service
Service 返回 IBinder
Client 通过 IBinder 调用 Service 的能力
它适合:
- Activity 控制音乐播放 Service。
- App 内部模块共享一个长期对象。
- 跨进程调用时暴露 AIDL 接口。
- 和其他应用组件建立持续连接。
2. 本地绑定:Local Binder
如果 Service 和客户端在同一个进程,最简单方式是自定义 Binder。
class DownloadService : Service() {
inner class LocalBinder : Binder() {
fun getService(): DownloadService = this@DownloadService
}
private val binder = LocalBinder()
override fun onBind(intent: Intent): IBinder {
return binder
}
fun startDownload(url: String) {
// 开始下载
}
fun getProgress(): Int {
return 50
}
}
Activity 绑定:
class MainActivity : AppCompatActivity() {
private var downloadService: DownloadService? = null
private var bound = false
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as DownloadService.LocalBinder
downloadService = binder.getService()
bound = true
}
override fun onServiceDisconnected(name: ComponentName?) {
downloadService = null
bound = false
}
}
override fun onStart() {
super.onStart()
bindService(
Intent(this, DownloadService::class.java),
connection,
Context.BIND_AUTO_CREATE
)
}
override fun onStop() {
if (bound) {
unbindService(connection)
bound = false
}
super.onStop()
}
}
3. 多客户端绑定
多个客户端可以同时绑定同一个 Service。
关键点:
- 第一个客户端绑定时,系统调用
onBind()。 - 系统会缓存
IBinder。 - 后续客户端绑定时,通常直接拿到同一个
IBinder,不再重复调用onBind()。 - 所有客户端都解绑后,系统调用
onUnbind()。 - 如果 Service 没有 started 状态,则随后销毁。
4. onServiceDisconnected 的含义
onServiceDisconnected() 不是每次 unbindService() 都会调用。
它通常表示连接意外断开,例如:
- Service 所在进程崩溃。
- Service 被系统杀死。
正常解绑时,不要依赖它做普通清理,普通清理应该放在调用 unbindService() 附近。
5. start + bind 的常见用法
音乐播放是典型例子:
startService:让音乐离开页面后继续播放
bindService:页面回来后重新控制播放进度、暂停、下一首
启动并绑定:
val intent = Intent(this, MusicService::class.java)
ContextCompat.startForegroundService(this, intent)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
退出页面只解绑:
unbindService(connection)
停止播放时再停止服务:
stopService(Intent(this, MusicService::class.java))
6. 跨进程绑定:AIDL 简述
如果客户端和 Service 不在同一个进程,或者要给其他 App 调用,就不能简单返回本地对象引用,需要使用 IPC。
常见方式:
| 方式 | 适合场景 |
|---|---|
| Messenger | 简单跨进程消息通信,串行处理 |
| AIDL | 复杂跨进程接口,支持多方法调用 |
| Binder 手写接口 | 系统级或高级场景 |
AIDL 简化理解:
定义 .aidl 接口 -> 编译生成 Binder Stub/Proxy -> Service 返回 Stub -> 客户端拿 Proxy 调用
7. 绑定服务常见坑
7.1 忘记解绑
bindService() 后必须配对 unbindService()。
常见生命周期配对:
| 绑定位置 | 解绑位置 |
|---|---|
onStart() |
onStop() |
onCreate() |
onDestroy() |
| 用户点击连接 | 用户点击断开/页面销毁 |
7.2 在 ServiceConnection 中持有 Activity 导致泄漏
匿名内部类如果长期被 Service 持有,可能间接持有 Activity。一般 App 内部绑定问题不大,但跨进程或复杂连接时要注意清理。
7.3 误解 onServiceDisconnected
正常调用 unbindService() 不一定触发 onServiceDisconnected()。
7.4 主线程阻塞
通过 Binder 调 Service 方法时,如果方法内部耗时,依旧可能阻塞调用方线程。不要在 Binder 方法里直接做耗时操作。
8. 面试回答模板
问题:bindService 是怎么通信的?
可以这样回答:
bindService 会让客户端和 Service 建立连接,Service 在 onBind 中返回一个 IBinder。客户端在 ServiceConnection 的 onServiceConnected 中拿到这个 Binder,然后通过 Binder 调用 Service 暴露的方法。同进程可以用自定义 LocalBinder,跨进程一般用 Messenger 或 AIDL。多个客户端可以同时绑定,系统通常只在第一个客户端绑定时调用 onBind 并缓存 IBinder,所有客户端解绑后,如果 Service 没有被 start,就会销毁。