MediaSession与MediaController
一、核心概念
Android 音频播放架构采用 C/S(客户端/服务端)模型:
| 组件 | 角色 | 类比 |
|---|---|---|
| MediaSession | 服务端 | 电视机 |
| MediaController | 客户端 | 遥控器 |
| MediaSession.Token | 连接凭证 | 遥控器的红外配对码 |
MediaSession运行在音频播放服务中,掌握播放器的实际控制权。
MediaController运行在 UI 层或外部应用,通过向 Session 发送指令来控制播放。
两者通过 Binder 机制实现跨进程通信。
二、原理详解
2.1 连接原理:如何拿到 MediaSession?
MediaController 与 MediaSession 的连接依赖 MediaSession.Token。
连接步骤:
1.服务端公开令牌:
-创建 MediaSession 时,系统自动生成唯一 Token。
-调用 mediaSession.setActive(true) 将 Session 注册到系统服务 MediaSessionManager。
-系统服务维护一个“活跃媒体会话列表”,供外部发现。
2.客户端获取令牌(两种方式):
-内部连接(同 App):Service 直接把 session.getSessionToken() 传给 Activity。
-外部发现(跨 App/系统):通过 MediaSessionManager.getActiveSessions() 获取列表,或通过 MediaBrowserService 连接。
3.建立双向通道:
-客户端调用 new MediaController(context, token) 完成绑定。
-底层通过 Binder 建立双向通信,Controller 发指令,Session 回推状态。
2.2 状态同步原理
状态同步采用 推送模型,即服务端主动推送变化,客户端被动接收。不走轮询。
同步的两个时机:
| 时机 | 方式 | 说明 |
|---|---|---|
| 刚连接时 | 全量拉取 | 调用controller.getMetadata()、getPlaybackState()获取当前快照 |
| 之后变化时 | 增量推送 | 通过注册的 Callback,由 Session 主动触发set方法实现同步 |
开发者必须手动更新的三项:
setMetadata() — 歌曲信息(标题、歌手、封面)
setPlaybackState() — 播放状态(播放/暂停/缓冲中),包括进度
setQueue() — 播放列表(如果需要)
| 问题 | 答案要点 |
|---|---|
| Token 错误/失效的原因? | 最常见是 Session 已release()但 Controller 没重建。必须每次onServiceConnected都拿新 Token 建新 Controller。 |
onSessionDestroyed()什么时候回调? | Session 被释放或服务进程死亡时。应在此回调中清理 UI、置空 Controller。 |
| 多个 Controller 同时连接会怎样? | 正常。Session 支持一对多,所有 Controller 都会收到推送。这是手表、通知栏、车载能同时同步的基础。 |
MediaBrowserService是 Android 提供的一个专门用于媒体内容浏览的服务基类。如果说 MediaSession 负责“播放控制”,那 MediaBrowserService 就负责“内容发现和浏览”。
MediaBrowserService = 内容发现入口 + 权限控制网关 + 向外部暴露 MediaSession 的桥梁。
二、核心作用
1. 提供可浏览的媒体内容树
onGetRoot():告诉客户端“你有权限看什么”。比如你的车机只想看音乐,不想看播客。
onLoadChildren():返回指定目录下的子项。点进“歌单”就返回歌曲列表。
2. 连接认证与权限控制
外部 App(如 Android Auto、Wear OS)通过 MediaBrowser 连接你的 MediaBrowserService 时,你可以在 onGetRoot() 里校验包名、签名,决定能访问什么内容或直接拒绝。
3. 配合 MediaSession 使用
安卓系统要求:如果要让 Android Auto 或 Wear OS 能够连接你的 App,只实现 MediaSession 是不够的,还必须实现 MediaBrowserService。
MediaBrowserService 帮助客户端发现你的 MediaSession.Token。客户端连接后,通过 getSessionToken() 拿到 Token,进而创建 MediaController 来控制播放。
