媒体按钮是 Android 设备和其他外围设备(例如蓝牙耳机上的暂停/播放按钮)上发现的硬件按钮。当用户按下媒体按钮时,Android 会生成一个 KeyEvent
,其中包含一个用于识别按钮的键码。媒体按钮 KeyEvents 的键码是常量,以 KEYCODE_MEDIA
开头(例如,KEYCODE_MEDIA_PLAY
)。
应用应该能够在三种情况下处理媒体按钮事件,优先级顺序如下:
- 当应用的 UI 活动可见时
- 当 UI 活动隐藏且应用的媒体会话处于活动状态时
- 当 UI 活动隐藏且应用的媒体会话处于非活动状态,并且需要重新启动时
在前台活动中处理媒体按钮
前台活动在其 onKeyDown()
方法中接收媒体按钮按键事件。根据 Android 的运行版本,系统将事件路由到媒体控制器的两种方式:
- 如果运行的是 Android 5.0(API 级别 21)或更高版本,请调用
FLAG_HANDLES_MEDIA_BUTTONS
MediaBrowserCompat.ConnectionCallback.onConnected
。这将自动调用媒体控制器的dispatchMediaButtonEvent()
,该方法将按键码转换为媒体会话回调。 - 在 Android 5.0(API 级别 21)之前,您需要修改
onKeyDown()
以自行处理事件。(有关详细信息,请参阅 在活动媒体会话中处理媒体按钮。)以下代码片段显示了如何拦截按键码并调用 dispatchMediaButtonEvent()。请务必返回true
以指示事件已处理Kotlin
fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return super.onKeyDown(keyCode, event) } when (keyCode) { KeyEvent.KEYCODE_MEDIA_PLAY -> { yourMediaController.dispatchMediaButtonEvent(event) return true } } return super.onKeyDown(keyCode, event) }
Java
@Override boolean onKeyDown(int keyCode, KeyEvent event) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return super.onKeyDown(keyCode, event); } switch (keyCode) { case KeyEvent.KEYCODE_MEDIA_PLAY: yourMediaController.dispatchMediaButtonEvent(event); return true; } return super.onKeyDown(keyCode, event); }
查找媒体会话
如果前台活动未处理事件,Android 将尝试查找可以处理该事件的媒体会话。同样,根据 Android 的运行版本,有两种方法可以搜索媒体会话
如果运行的是 Android 8.0(API 级别 26)或更高版本,系统将尝试查找最近使用 MediaSession 本地播放音频的应用。如果会话仍在活动状态,Android 会直接将事件发送到该会话。否则,如果会话未处于活动状态且具有媒体按钮接收器,则 Android 会将事件发送到接收器,该接收器将重新启动会话,以便它可以接收事件。(有关详细信息,请参阅 使用媒体按钮重新启动非活动媒体会话。)如果会话没有媒体按钮接收器,则系统会丢弃媒体按钮事件,并且不会发生任何事情。逻辑如下图所示
在 Android 8.0(API 级别 26)之前,系统会尝试将事件发送到活动媒体会话。如果有多个活动媒体会话,Android 会尝试选择正在准备播放(缓冲/连接)、播放或暂停的媒体会话,而不是已停止的媒体会话。(有关详细信息,请参阅 在活动媒体会话中处理媒体按钮。)如果没有活动会话,Android 会尝试将事件发送到最近的活动会话。(有关详细信息,请参阅 使用媒体按钮重新启动非活动媒体会话。)逻辑如下图所示
在活动媒体会话中处理媒体按钮
在 Android 5.0(API 级别 21)及更高版本中,Android 通过调用 onMediaButtonEvent()
自动将媒体按钮事件分派到您的活动媒体会话。默认情况下,此回调会将 KeyEvent 转换为与按键码匹配的相应媒体会话回调方法。
在 Android 5.0(API 级别 21)之前,Android 通过广播具有 ACTION_MEDIA_BUTTON
操作的意图来处理媒体按钮事件。您的应用必须注册 BroadcastReceiver 以拦截这些意图。MediaButtonReceiver
类专门为此目的而设计。它是 Android 媒体兼容库 中的一个便捷类,用于处理 ACTION_MEDIA_BUTTON
并将传入的意图转换为相应的 MediaSessionCompat.Callback
方法调用。
MediaButtonReceiver
是一个短暂的 BroadcastReceiver。它将传入的意图转发到管理媒体会话的服务。**如果您想在 Android 5.0 之前的系统中使用媒体按钮,则必须在清单中包含 MediaButtonReceiver
,并使用 MEDIA_BUTTON
意图过滤器。**
<receiver android:name="android.support.v4.media.session.MediaButtonReceiver" >
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
BroadcastReceiver
将意图转发到您的服务。要解析意图并生成对媒体会话的回调,请在服务的 onStartCommand()
中包含 MediaButtonReceiver.handleIntent()
方法。这会将按键码转换为相应的会话回调方法。
Kotlin
private val mediaSessionCompat: MediaSessionCompat = ... override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { MediaButtonReceiver.handleIntent(mediaSessionCompat, intent) return super.onStartCommand(intent, flags, startId) }
Java
private MediaSessionCompat mediaSessionCompat = ...; public int onStartCommand(Intent intent, int flags, int startId) { MediaButtonReceiver.handleIntent(mediaSessionCompat, intent); return super.onStartCommand(intent, flags, startId); }
使用媒体按钮重新启动非活动媒体会话
如果 Android 可以识别最后一个活动媒体会话,它会尝试通过将 ACTION_MEDIA_BUTTON
意图发送到清单注册的组件(例如服务或 BroadcastReceiver
)来重新启动会话。
这允许您的应用在 UI 不可见时重新启动播放,这对于大多数音频应用来说都是如此。
当您使用 MediaSessionCompat
时,此行为会自动启用。如果您使用 Android 框架的 MediaSession
或支持库 24.0.0 到 25.1.1,则必须调用 setMediaButtonReceiver
以允许媒体按钮重新启动非活动媒体会话。
您可以在 Android 5.0(API 级别 21)及更高版本中通过设置空媒体按钮接收器来禁用此行为
Kotlin
// Create a MediaSessionCompat mediaSession = MediaSessionCompat(context, LOG_TAG) mediaSession.setMediaButtonReceiver(null)
Java
// Create a MediaSessionCompat mediaSession = new MediaSessionCompat(context, LOG_TAG); mediaSession.setMediaButtonReceiver(null);
自定义媒体按钮处理程序
onMediaButtonEvent()
的默认行为会提取按键码,并使用媒体会话的当前状态和支持的操作列表来确定要调用的方法。例如,KEYCODE_MEDIA_PLAY
会调用 onPlay()
。
为了在所有应用中提供一致的媒体按钮体验,您应该使用默认行为,并且仅为特定目的偏离。如果媒体按钮需要自定义处理,请覆盖回调的 onMediaButtonEvent()
方法,使用 intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT)
提取 KeyEvent
,自行处理事件,并返回 true
。
总结
要在所有版本的 Android 中正确处理媒体按钮事件,必须在创建媒体会话时指定 FLAG_HANDLES_MEDIA_BUTTONS
。
此外,根据您计划支持的 Android 版本,您还必须满足以下要求
在 Android 5.0 或更高版本中运行时
- 从媒体控制器
onConnected()
回调中调用MediaControllerCompat.setMediaController()
- 要允许媒体按钮重新启动非活动会话,请通过调用
setMediaButtonReceiver()
并传递PendingIntent
来动态创建MediaButtonReceiver
在 Android 5.0 之前的系统中运行时
- 覆盖活动的
onKeyDown()
以处理媒体按钮 - 通过将其添加到应用的清单中来静态创建
MediaButtonReceiver