4. MediaSessionToken分发:创建与传递、客户端与服务端通信链路
4.1 原理概述
MediaSessionToken是Android多媒体框架中用于标识和验证媒体会话的唯一令牌。当Service侧创建MediaSession实例时,系统会为其生成一个全局唯一的。该通过Binder跨进程传递给客户端(如Activity或MediaBrowserService),客户端利用这个与真正的会话对象建立通信链路。整个分发机制的核心目的:确保客户端只能通过合法访问对应的媒体会话,避免无授权访问或会话劫持。
关键概念:MediaSessionToken本质上是一个Parcelable对象,内部封装了IControllerBinder接口的Binder引用。客户端拿到后,通过
MediaSession.#getController()获取MediaController实例,从而发送播放命令、接收回调。
4.2 创建流程
4.2.1 Service端创建
在MediaBrowserService或自定义Service中,创建MediaSession的典型代码:
MediaSession mediaSession = new MediaSession(context, "MyMediaSession");
mediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
// 设置回调、播放器等
mediaSession.setCallback(new MediaSession.Callback() { ... });
// 此时已创建,可通过mediaSession.getSessionToken()获取
MediaSession. = mediaSession.getSessionToken();
系统内部流程:MediaSession构造函数会注册到MediaSessionManager,并分配一个SessionRecord。的创建时机就在此时——SessionTokenImpl持有IControllerBinder的Binder引用,并序列化自身。
4.2.2 的内部结构
| 字段 | 类型 | 说明 |
|---|---|---|
| mPackageName | String | 创建Session的应用包名 |
| mServiceName | String | Session所在Service组件名 |
| mControllerBinder | IBinder | 与MediaController通信的代理接口Binder |
| mUid | int | 创建Session的UID |
| mUserHandle | UserHandle | 用户句柄 |
注意:对象是Parcelable,跨进程传输时需注意序列化/反序列化性能。频繁在进程间传递大体积可能导致ANR,应只在启动时传递一次并缓存。
4.3 客户端获取的两种途径
- 通过Binder直接返回:在
MediaBrowserService#onGetRoot()返回的BrowserRoot中携带(实际上BrowserRoot内部包含)。 - 通过MediaBrowser连接流程:客户端
MediaBrowser连接成功后,通过MediaBrowser#getSessionToken()获得。
典型客户端连接代码:
// 客户端(如Activity)中
MediaBrowser mediaBrowser = new MediaBrowser(context,
new ComponentName(this, MusicService.class),
new MediaBrowser.ConnectionCallback() {
@Override
public void onConnected() {
MediaSession. = mediaBrowser.getSessionToken();
MediaController controller = new MediaController(context, );
setMediaController(controller);
}
}, null);
mediaBrowser.connect();
4.4 通信链路建立(Binder + )
当客户端拿到后,调用new MediaController(context, )时,内部会通过.getController()获取底层MediaControllerImplBase。该实现类利用中存储的IControllerBinder的Binder引用,直接与Service侧的MediaSessionRecord进行双向Binder通信:
- 客户端 → 服务端:通过Binder调用
sendCommand()、dispatchMediaButtonEvent()等。 - 服务端 → 客户端:通过
IControllerCallback回调播放状态、队列变化等。
链路核心:不传输播放数据,只传输控制权限。媒体数据(如音频流)通常通过另一个通道(如MediaPlayer、ExoPlayer的内部渲染器)处理,与控制链路分离。
4.5 分发SVG流程图
4.6 调试方法
- 日志验证是否一致:在Service端输出的hashCode(),客户端获取后比对。
- 使用adb命令查看Session状态:
adb shell dumpsys media_session可列出所有活跃的MediaSession及其。 - 检查Binder死亡通知:监听
MediaControllerCompat#registerCallback()中的onSessionDestroyed(),若异常触发说明失效或Service销毁。
常见调试代码片段:
// Service侧打印
Log.d("MediaSessionDemo", " created: " + mediaSession.getSessionToken().toString());
// 客户端侧打印
MediaSession. = mediaBrowser.getSessionToken();
Log.d("MediaSessionDemo", "Client received : " + .toString());
// 正常情况下两个toString输出应当相同(内部包含Binder的描述)
4.7 常见错误与稳定性优化
| 错误场景 | 原因 | 解决方案 |
|---|---|---|
| 客户端getSessionToken()返回null | MediaBrowser尚未完全连接就在onConnected之前调用了getSessionToken | 确保回调中才获取 |
| 创建MediaController时抛出IllegalStateException | 已被回收或Service进程已死 | 增加重连机制,在onSessionDestroyed时重建连接 |
| Binder通信异常(DeadObjectException) | Service进程崩溃或被杀死 | 实现MediaController.Callback#onSessionDestroyed()进行重连 |
| 多进程场景传递失败 | 未正确实现Parcelable或Binder引用丢失 | 确保使用官方MediaSession.,不要手动创建副本 |
性能优化建议:
1. 避免在频繁回调中反复通过getSessionToken()获取新实例——一旦创建应缓存重用。
2. 跨进程传递时,如果客户端长时间不使用时,及时释放对应MediaController以减少Binder引用数量。
3. 对于需要频繁交互的场景(如游戏后台播放),建议在Service侧使用
1. 避免在频繁回调中反复通过getSessionToken()获取新实例——一旦创建应缓存重用。
2. 跨进程传递时,如果客户端长时间不使用时,及时释放对应MediaController以减少Binder引用数量。
3. 对于需要频繁交互的场景(如游戏后台播放),建议在Service侧使用
MediaSessionCompat的池机制。