参考资料
媒体应用架构概览 | Android 开发者 | Android Developers
MediaSession | Android Developers
MediaSession框架全解析_qzns木雨的博客-优快云博客_mediasession
1. 多媒体应用架构
1.1 传统应用架构
播放音频或视频的多媒体应用通常由两部分组成:
- 播放器:接收传入的数据多媒体,并输出音频或视频。可以是MediaPlayer、ExoPlayer或其他Player。
- 界面:用于显示、控制播放器状态界面。

众所周知,如果需要在应用的后台继续播放音频,我们就需要把Player放置在Service中,那么界面与播放器之间通信就非常值得研究了。很长一段时间里,都是由Service提供一个Binder来实现与播放器之间的通信。但是往往下拉的状态栏和桌面的Widget都需要与Service之间进行通信,这时候Service就不得不通过实现一系列AIDL接口/广播/ContentProvider完成与其它应用之间的通信,而这些通信手段既增加了应用开发者之间的沟通成本,也增加了应用之间的耦合度。
为了解决上面的问题,Android官方从Android5.0开始提供了MediaSession框架。
1.2 MediaSession 框架
MediaSession框架规范了音视频应用中界面与播放器之间的通信接口,实现界面与播放器之间的完全解耦。框架定义了两个重要的类媒体会话和媒体控制器,它们为构建多媒体播放器应用提供了一个完善的结构。
媒体会话和媒体控制器通过以下方式相互通信:使用与标准播放器操作(播放、暂停、停止等)相对应的预定义回调,以及用于定义应用独有的特殊行为的可扩展自定义调用。

2. MediaSession 介绍
MediaSession框架属于典型的C/S架构,有四个常用的成员类,是整个MediaSession框架流程控制的核心。
2.1 客户端媒体浏览器 - MediaBrowser
媒体浏览器,用来连接MediaBrowserService和订阅数据,通过它的回调接口我们可以获取与Service的连接状态以及获取在Service中的音乐库数据。在客户端(也就是上文我们提到的界面,或者说是控制端)中创建。
媒体浏览器不是线程安全的。所有调用都应在构造MediaBrowser的线程上进行。
@RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val component = ComponentName(this, MediaService::class.java)
mMediaBrowser = MediaBrowser(this, component, connectionCallback, null);
mMediaBrowser.connect()
}
2.1.1 MediaBrowser.ConnectionCallback
用于接收与MediaBrowserService连接事件的回调,在创建MediaBrowser时传入。
@RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val component = ComponentName(this, MediaService::class.java)
mMediaBrowser = MediaBrowser(this, component, connectionCallback, null);
mMediaBrowser.connect()
}
private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
override fun onConnected() {
super.onConnected()
}
override fun onConnectionFailed() {
super.onConnectionFailed()
}
override fun onConnectionSuspended() {
super.onConnectionSuspended()
}
}
2.1.2 MediaBrowser.ItemCallback
用于返回MediaBrowser.getItem()的结果。
private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
override fun onConnected() {
super.onConnected()
// ...
if(mMediaBrowser.isConnected) {
val mediaId = mMediaBrowser.root
mMediaBrowser.getItem(mediaId, itemCallback)
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
private val itemCallback = object : MediaBrowser.ItemCallback(){
override fun onItemLoaded(item: MediaBrowser.MediaItem?) {
super.onItemLoaded(item)
}
override fun onError(mediaId: String) {
super.onError(mediaId)
}
}
2.1.3 MediaBrowser.MediaItem
包含有关单个媒体项的信息,用于浏览/搜索媒体。MediaItem依赖于服务端提供,因此框架本身无法保证它包含的值都是正确的。
2.1.4 MediaBrowser.SubscriptionCallback
用于订与MediaBrowserService中MediaBrowser.MediaItem列表变化的回调。
private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
override fun onConnected() {
super.onConnected()
// ...
if(mMediaBrowser.isConnected) {
val mediaId = mMediaBrowser.root
// 需要先取消订阅
mMediaBrowser.unsubscribe(mediaId)
// 服务端会调用onLoadChildren
mMediaBrowser.subscribe(mediaId, subscribeCallback)
}
}
}
private val subscribeCallback = object : MediaBrowser.SubscriptionCallback(){
override fun onChildrenLoaded(
parentId: String,
children: MutableList<MediaBrowser.MediaItem>
) {
super.onChildrenLoaded(parentId, children)
}
override fun onChildrenLoaded(
parentId: String,
children: MutableList<MediaBrowser.MediaItem>,
options: Bundle
) {
super.onChildrenLoaded(parentId, children, options)
}
override fun onError(parentId: String) {
super.onError(parentId)
}
override fun onError(parentId: String, options: Bundle) {
super.onError(parentId, options)
}
}
2.2 客户端媒体控制器 - MediaController
媒体控制器,用来向服务端发送控制指令,例如:播放、暂停等等,在客户端中创建。媒体控制器是线程安全的。MediaController还有一个关联的权限android.permission.MEDIA_CONTENT_CONTROL(不是必须加的权限)必须是系统级应用才可以获取,幸运的是车载应用一般都是系统级应用。
MediaController必须在MediaBrowser连接成功后才可以创建。
private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
override fun onConnected() {
super.onConnected()
// ...
if(mMediaBrowser.isConnected) {
val sessionToken = mMediaBrowser.sessionToken
mMediaController = MediaController(applicationContext,sessionToken)
}
}
}
2.2.1 MediaController.Callback
用于从MediaSession接收回调。使用方式如下:
private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
override fun onConnected() {
super.onConnected()
// ...
if(mMediaBrowser.isConnected) {
val sessionToken = mMediaBrowser.sessionToken
mMediaController = MediaController(applicationContext,sessionToken)
mMediaController.registerCallback(controllerCallback)
}
}
}
private val controllerCallback = object : MediaController.Callback() {
override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
super.onAudioInfoChanged(info)
}
override fun onExtrasChanged(extras: Bundle?) {
super.onExtrasChanged(extras)
}
// ...
}
2.2.2 MediaController.PlaybackInfo
保存有关当前播放以及如何处理此会话的音频的信息。使用方式如下:
// 获取当前回话播放的音频信息
val playbackInfo = mMediaController.playbackInfo
2.2.3 MediaController.TransportControls
用于控制会话中媒体播放的接口。这允许客户端向Session发送媒体控制命令。使用方式如下:
private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
override fun onConnected() {
super.onConnected()
// ...
if(mMediaBrowser.isConnected) {
val sessionToken = mMediaBrowser.sessionToken
mMediaController = MediaController(applicationContext,sessionToken)
// 播放媒体
mMediaController.transportControls.play()
// 暂停媒体
mMediaController.transportControls.pause()
}
}
}
2.3 服务端媒体浏览服务 - MediaBrowserService
媒体浏览器服务,继承自Service,MediaBrowserService属于服务端,也是承载播放器(如MediaPlayer、ExoPlayer等)和MediaSession的容器。
实现MediaBrowserService时会要求复写onGetRoot和onLoadChildren两个方法。
onGetRoot通过的返回值决定是否允许客户端的MediaBrowser连接到MediaBrowserService。
当客户端调用MediaBrowser.subscribe时会触发onLoadChildren方法。
const val FOLDERS_ID = "__FOLDERS__"
const val ARTISTS_ID = "__ARTISTS__"
const val ALBUMS_ID = "__ALBUMS__"
const val GENRES_ID = "__GENRES__"
const val ROOT_ID = "__ROOT__"
class MediaService : MediaBrowserService() {
override fun onGetRoot(
clientPackageName: String,
clientUid: Int,
rootHints: Bundle?
): BrowserRoot? {
// 由MediaBrowser.connect触发,可以通过返回null拒绝客户端的连接。
return BrowserRoot(ROOT_ID, null)
}
override fun onLoadChildren(
parentId: String,
result: Result<MutableList<MediaBrowser.MediaItem>>
) {
// 由MediaBrowser.subscribe触发
when (parentId) {
ROOT_ID -> {
// 查询本地媒体库
// ...
// 将此消息与当前线程分离,并允许稍后进行sendResult调用

本文详细解读了Android MediaSession框架,包括架构、组件间的通信机制,以及客户端和服务端如何通过MediaBrowser、MediaController和MediaSession进行交互。重点讲解了MediaSession的使用步骤和核心类功能,有助于开发者理解和实现代理车载多媒体应用。
最低0.47元/天 解锁文章
1429

被折叠的 条评论
为什么被折叠?



