Android 13 接入 MediaSession 详细文档
Android 13 接入 MediaSession 详细文档
一、MediaSession 概述
传统音乐播放应用架构需优先保障音频后台播放,传统方案依赖独立Service异步加载资源并处理播放控制,通过Binder或广播实现界面通信。扩展通知栏控制需额外构建广播接收器,锁屏交互则依赖AIDL等跨进程技术,多终端协同更导致架构复杂化。
MediaSession框架通过C/S架构解耦界面与服务层,核心组件包含MediaSession和MediaController,提供标准化操作接口(播放/暂停等)及自定义扩展能力。Android Support Library v4提供兼容包(类名含Compat后缀),MediaBrowser与MediaBrowserCompat功能一致。该架构简化交互流程、降低多端适配成本并提升开发效率。
二、接入准备
在 AndroidManifest.xml 文件中添加必要的权限声明:
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
注意:MEDIA_CONTENT_CONTROL 权限通常只能授予系统应用或通过特殊方式授权的应用。对于大多数媒体应用来说,仅需要正确实现 MediaSession 即可,无需此权限。
确保你的项目已包含最新的媒体相关依赖:
implementation 'androidx.media:media:1.6.0' // 使用最新稳定版本
三、创建 MediaSession
在应用的主 Activity 或 Service 中创建 MediaSession 实例:
// 创建 MediaSessionCompat 实例
MediaSessionCompat mediaSession = new MediaSessionCompat(context, "YourMediaSessionTag");// 设置回调接口
mediaSession.setCallback(new MediaSessionCompat.Callback() {@Overridepublic void onPlay() {super.onPlay();// 处理播放逻辑}@Overridepublic void onPause() {super.onPause();// 处理暂停逻辑}// 实现其他需要的回调方法...
});// 设置会话令牌,供其他组件使用
mediaSession.setSessionToken(sessionToken);// 设置初始播放状态
PlaybackStateCompat state = new PlaybackStateCompat.Builder().setActions(getAvailableActions()).setState(PlaybackStateCompat.STATE_PAUSED, 0, 1.0f).build();
mediaSession.setPlaybackState(state);// 设置元数据
MediaMetadataCompat metadata = new MediaMetadataCompat.Builder().putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Song Title").putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "Artist Name").putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArtBitmap).build();
mediaSession.setMetadata(metadata);// 激活会话
mediaSession.setActive(true);
在 Android 13 中,建议添加以下配置以优化用户体验:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {// 设置媒体会话的锁屏可见性mediaSession.setSessionActivity(PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT));// 设置媒体会话的交互模式mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
}
四、处理媒体控制
在 MediaSession.Callback 中实现各种媒体控制逻辑:
@Override
public void onPlay() {// 开始播放媒体if (!isPlaying()) {startPlayback();updatePlaybackState(PlaybackStateCompat.STATE_PLAYING);}
}@Override
public void onPause() {// 暂停媒体播放if (isPlaying()) {pausePlayback();updatePlaybackState(PlaybackStateCompat.STATE_PAUSED);}
}@Override
public void onSkipToNext() {// 跳转到下一首skipToNext();updateMetadata(currentMediaMetadata);
}@Override
public void onSkipToPrevious() {// 跳转到上一首skipToPrevious();updateMetadata(currentMediaMetadata);
}@Override
public void onSeekTo(long pos) {// 跳转到指定位置seekTo(pos);updatePlaybackState(currentState, pos);
}
Android 13 支持添加自定义操作按钮:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {// 添加自定义操作PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder().setActions(getAvailableActions());// 添加自定义操作,例如"喜欢/收藏"stateBuilder.addCustomAction(new PlaybackStateCompat.CustomAction.Builder("ACTION_LIKE", "Like", R.drawable.ic_like).build());mediaSession.setPlaybackState(stateBuilder.build());
}// 处理自定义操作
@Override
public void onCustomAction(String action, Bundle extras) {if ("ACTION_LIKE".equals(action)) {toggleLikeStatus();}super.onCustomAction(action, extras);
}
五、与系统媒体控制集成
创建与 MediaSession 关联的通知:
// 创建通知渠道(Android 8.0+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {NotificationChannel channel = new NotificationChannel("media_playback_channel","Media Playback",NotificationManager.IMPORTANCE_LOW);channel.setShowBadge(false);getSystemService(NotificationManager.class).createNotificationChannel(channel);
}// 构建通知
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "media_playback_channel").setSmallIcon(R.drawable.ic_music_note).setContentTitle("Song Title").setContentText("Artist Name").setLargeIcon(albumArtBitmap).setStyle(new androidx.media.app.NotificationCompat.MediaStyle().setMediaSession(mediaSession.getSessionToken()).setShowActionsInCompactView(0, 1, 2)) // 显示前三个操作按钮.setVisibility(NotificationCompat.VISIBILITY_PUBLIC).setPriority(NotificationCompat.PRIORITY_LOW);// 添加控制按钮
builder.addAction(new NotificationCompat.Action(R.drawable.ic_previous, "Previous", MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)));builder.addAction(new NotificationCompat.Action(isPlaying() ? R.drawable.ic_pause : R.drawable.ic_play, isPlaying() ? "Pause" : "Play", MediaButtonReceiver.buildMediaButtonPendingIntent(context, isPlaying() ? PlaybackStateCompat.ACTION_PAUSE : PlaybackStateCompat.ACTION_PLAY)));builder.addAction(new NotificationCompat.Action(R.drawable.ic_next, "Next", MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_SKIP_TO_NEXT)));// 显示通知
NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, builder.build());
确保锁屏界面正确显示媒体控件:
// 在 MediaSession.Callback 中更新元数据时,系统会自动更新锁屏界面
// 但可以进一步自定义锁屏界面行为
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {// 设置锁屏可见性mediaSession.setSessionActivity(PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT));
}
六、处理音频焦点
在 Android 13 中,正确处理音频焦点对于提供良好的用户体验至关重要:
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);// 请求音频焦点
AudioManager.AudioFocusRequest focusRequest = new AudioManager.AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).setAudioAttributes(new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build()).setAcceptsDelayedFocusGain(true).setWillPauseWhenDucked(true).setOnAudioFocusChangeListener((focusChange) -> {switch (focusChange) {case AudioManager.AUDIOFOCUS_GAIN:// 恢复播放resumePlayback();break;case AudioManager.AUDIOFOCUS_LOSS:// 停止播放并释放资源stopPlayback();break;case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:// 暂停播放pausePlayback();break;case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:// 降低音量setVolume(0.5f);break;}}).build();int result = audioManager.requestAudioFocus(focusRequest);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {// 音频焦点请求成功,可以开始播放startPlayback();
}
七、测试与调试
确保覆盖以下测试场景:
- 设备锁屏状态下控制媒体播放
- 多任务切换时保持播放状态
- 连接蓝牙耳机/车载设备时的控制
- 通知栏控制响应
- 音频焦点变化处理
- 自定义操作按钮响应
- 使用 Android Studio 的 Logcat 查看 MediaSession 相关日志
- 通过 adb 命令模拟媒体按键事件:
adb shell input keyevent KEYCODE_MEDIA_PLAY_PAUSE
adb shell input keyevent KEYCODE_MEDIA_NEXT
adb shell input keyevent KEYCODE_MEDIA_PREVIOUS
- 检查 Notification 是否正确显示媒体信息
- 验证不同 Android 版本的行为一致性
八、Android 13 新特性适配
Android 13 引入了更精细的媒体会话控制:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {// 设置媒体会话的交互模式mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS |MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS);// 设置媒体会话的扩展信息Bundle extras = new Bundle();extras.putString("com.example.custom_key", "custom_value");mediaSession.setExtras(extras);
}
Android 13 加强了隐私保护,注意处理相关权限:
// 在 AndroidManifest.xml 中声明媒体内容控制权限(如果需要)
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" tools:ignore="ProtectedPermissions" />
注意:此权限通常只能由系统应用使用
对于第三方应用,应依赖 MediaSession 的标准 API 实现功能
九、性能优化建议
- 资源管理:在 Activity/Service 销毁时正确释放 MediaSession 资源
- 电池优化:避免不必要的唤醒和后台播放
- 内存泄漏:确保 MediaSession 回调中的引用不会导致内存泄漏
- 响应速度:快速响应媒体控制事件,避免用户操作延迟
十、参考资源
- https://developer.android.com/reference/androidx/media/session/MediaSessionCompat
- https://developer.android.com/guide/topics/media-apps/audio-focus
- https://developer.android.com/training/notify-user/build-notification#media-style
通过遵循本文档中的指导,开发者可以确保其 Android 13 应用中的媒体播放功能与系统深度集成,提供一致且高质量的用户体验。