Android 媒体通知终极开发指南:深入 MediaSession 与状态管理
本教程将彻底剖析 Android 媒体通知系统的每个技术细节,包含完整的生命周期处理、精确的状态同步机制以及高级自定义功能的实现方案。
1、核心架构深度解析
1.1、MediaSessionCompat 完整初始化流程
fun createMediaSession(context: Context): MediaSessionCompat {
val componentName = ComponentName(context, MediaReceiver::class.java)
return MediaSessionCompat(
context,
"MusicService",
componentName,
PendingIntent.getBroadcast(context, 0, Intent(ACTION_MEDIA_BUTTON),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
).apply {
// 必须设置的标志位组合
setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS or
MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS
)
// 连接 Service 的生命周期
setSessionActivity(createPendingIntent(context))
// 音频焦点处理回调
setCallback(object : MediaSessionCompat.Callback() {
override fun onCommand(command: String, extras: Bundle?, cb: ResultReceiver?) {
when(command) {
CUSTOM_ACTION_LIKE -> handleLikeAction()
else -> super.onCommand(command, extras, cb)
}
}
// 重写全部15个核心回调方法...
})
}
}
1.2、会话令牌传播机制
关键路径:
MediaBrowserService.onGetRoot() 返回包含 token 的 BrowserRoot
Activity 通过 MediaControllerCompat.setMediaController()
Notification 通过 MediaStyle.setMediaSession()
2、精准状态管理系统
2.1、PlaybackStateCompat 完全参数手册
new PlaybackStateCompat.Builder()
// 基础状态三元组
.setState(
STATE_PLAYING, // 六种预设状态之一
positionMillis, // 精确到毫秒的进度
playbackSpeed // 支持变速播放(1.0=正常速度)
)
// 可用操作掩码(共32种标准动作)
.setActions(
PlaybackStateCompat.ACTION_PLAY_PAUSE |
PlaybackStateCompat.ACTION_SKIP_TO_NEXT |
PlaybackStateCompat.ACTION_SEEK_TO
)
// 错误处理系统
.setErrorMessage(
ERROR_CODE_AUTHENTICATION_EXPIRED,
getString(R.string.login_required)
)
// 自定义行为集合
.addCustomAction(
new PlaybackStateCompat.CustomAction.Builder(
"com.example.LIKE",
getString(R.string.like),
R.drawable.ic_like
).setExtras(Bundle().apply {
putBoolean("is_liked", false)
}).build()
)
// 活跃操作的标记(用于指示当前正在进行的操作)
.setActiveQueueItemId(queueItemId)
.build();
2.2、状态同步时序控制
class PlaybackMonitor(
private val session: MediaSessionCompat,
private val player: ExoPlayer
) {
private var updateRunnable = object : Runnable {
override fun run() {
val currentState = when {
player.isLoading -> STATE_BUFFERING
player.isPlaying -> STATE_PLAYING
else -> STATE_PAUSED
}
session.setPlaybackState(
PlaybackStateCompat.Builder()
.setState(currentState, player.currentPosition, player.playbackParameters.speed)
.setBufferedPosition(player.bufferedPosition)
.setLastPositionUpdateTime(SystemClock.elapsedRealtime())
.build()
)
handler.postDelayed(this, STATE_UPDATE_INTERVAL_MS)
}
}
fun startMonitoring() {
player.addListener(object : Player.Listener {
override fun onEvents(player: Player, events: Player.Events) {
if (events.containsAny(EVENT_POSITION_CHANGED, EVENT_PLAYBACK_STATE_CHANGED)) {
handler.removeCallbacks(updateRunnable)
updateRunnable.run()
}
}
})
}
} 关键技术点:
使用 SystemClock.elapsedRealtime() 保证多设备时间基准一致
300ms 是最佳状态更新间隔(平衡流畅度与耗电)
缓冲进度单独通过 setBufferedPosition() 更新
3、自定义 Action 高阶实现
3.1、动态 CustomAction 工作流
// 步骤1:定义可序列化的Action ID
public interface CustomActions {
String LIKE = "like_action";
String DISLIKE = "dislike_action";
}
// 步骤2:构建带状态的Action
private PlaybackStateCompat.CustomAction buildLikeAction(boolean isLiked) {
return new PlaybackStateCompat.CustomAction.Builder(
CustomActions.LIKE,
context.getString(isLiked ? R.string.liked : R.string.unliked),
isLiked ? R.drawable.ic_heart_filled : R.drawable.ic_heart_outline
)
.setExtras(new Bundle().apply {
putBoolean("liked_status", isLiked)
})
.build();
}
// 步骤3:在回调中处理点击
override fun onCustomAction(action: String, extras: Bundle?) {
when(action) {
CustomActions.LIKE -> {
val currentStatus = extras?.getBoolean("liked_status") ?: false
api.toggleLike(!currentStatus)
updatePlaybackStateWithNewAction(buildLikeAction(!currentStatus))
}
}
}
3.2、突破图标颜色限制的方案
虽然系统会强制对图标着色,但可以通过以下方式增强表现力:
分层矢量图技术
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorAccent"> <!-- 第一层:系统着色 -->
<group android:pivotX="12" android:pivotY="12">
<path android:fillColor="#FF0000" <!-- 第二层:保留红色 -->
android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5..." />
</group>
</vector>
动态替换技术
notificationBuilder.addAction(
NotificationCompat.Action.Builder(
Icon.createWithAdaptiveBitmap(
createColoredBitmap(R.drawable.ic_base, Color.RED) // 运行时生成位图
),
"Favorite",
buildPendingIntent(CustomActions.LIKE)
).build()
)
private fun createColoredBitmap(drawableId: Int, color: Int): Bitmap {
val drawable = AppCompatResources.getDrawable(context, drawableId)!!
val bitmap = Bitmap.createBitmap(48.dpToPx(), 48.dpToPx(), ARGB_8888)
Canvas(bitmap).apply {
drawable.setBounds(0, 0, width, height)
drawable.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
color, BlendModeCompat.SRC_ATOP
)
drawable.draw(this)
}
return bitmap
}
4、完整示例工程结构
app/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── media/
│ │ │ │ ├── MediaSessionHolder.kt # 会话管理中心
│ │ │ │ ├── PlaybackStateMachine.kt # 状态引擎
│ │ │ │ └── NotiBuilder.kt # 通知构造器
│ │ │ └── service/
│ │ │ └── MusicService.kt # 后台服务
│ │ └── res/
│ │ ├── xml/
│ │ │ └── media_buttons.xml # 硬件按键映射
└── └── └── layout/
└── custom_noti_actions.xml # 备用自定义布局