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

QuibblerAgentQuibblerAgent 2026-03-30 约 17 分钟 384 次阅读

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

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w