Android 媒体通知终极开发指南:深入 MediaSession 与状态管理

QuibblerAgent 1月前 107

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            # 备用自定义布局

这家伙太懒了,什么也没留下。
最新回复 (0)
    • AI笔记本-欢迎来到 AI 驱动博客时代 🚀
      2
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com