如何在MediaBrowserServiceCompat中访问SimpleExoPlayer实例
ExoPlayer + MediaBrowserServiceCompat 实现方案
核心逻辑不需要序列化传递播放器实例:ExoPlayer 实例全局唯一托管在 MediaBrowserServiceCompat 中,Activity 通过绑定服务直接获取同进程下的播放器对象引用,再绑定给 PlayerView 即可,完全符合官方架构要求。
步骤1:迁移播放器初始化逻辑到Service
移除 Activity 中创建 SimpleExoPlayer 的代码,把所有播放器初始化、MediaSession 关联逻辑放到你的 MediaBrowserServiceCompat 实现类中:
class PodcastPlayService : MediaBrowserServiceCompat() { lateinit var exoPlayer: SimpleExoPlayer private set private lateinit var mediaSession: MediaSessionCompat private lateinit var notificationManager: PlayerNotificationManager private val NOTIFICATION_ID = 1001 inner class LocalBinder : Binder() { fun getService() = this@PodcastPlayService } override fun onBind(intent: Intent?): IBinder { return LocalBinder() } override fun onCreate() { super.onCreate() // 初始化MediaSession,作为系统媒体控件、通知栏的交互枢纽 mediaSession = MediaSessionCompat(this, "PodcastPlay").apply { isActive = true setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS ) } sessionToken = mediaSession.sessionToken // 原Activity中的ExoPlayer初始化逻辑挪到这里 val renderersFactory = DefaultRenderersFactory(this) val trackSelectionFactory = AdaptiveTrackSelection.Factory() val trackSelector = DefaultTrackSelector(trackSelectionFactory) val loadControl = DefaultLoadControl() exoPlayer = ExoPlayerFactory.newSimpleInstance(this, renderersFactory, trackSelector, loadControl) // 用ExoPlayer官方提供的MediaSessionConnector自动同步播放状态到MediaSession // 不需要自己写播放状态、进度、媒体按钮事件的转发逻辑 MediaSessionConnector(mediaSession).setPlayer(exoPlayer) // 初始化通知栏管理器,自动生成锁屏、通知栏播放控件 notificationManager = PlayerNotificationManager.createWithNotificationChannel( this, "podcast_play_channel", R.string.play_channel_name, NOTIFICATION_ID, object : PlayerNotificationManager.MediaDescriptionAdapter { override fun getCurrentContentTitle(player: Player): String { return player.currentMediaItem?.mediaMetadata?.title?.toString() ?: "正在播放" } override fun getCurrentContentText(player: Player): String? { return player.currentMediaItem?.mediaMetadata?.artist?.toString() } override fun getCurrentLargeIcon(player: Player, callback: PlayerNotificationManager.BitmapCallback): Bitmap? { // 加载播客封面,异步加载完成后调用callback.onBitmap(bitmap)即可 return null } override fun createCurrentContentIntent(player: Player): PendingIntent? { // 配置点击通知跳转回MainActivity的逻辑 return PendingIntent.getActivity( this@PodcastPlayService, 0, Intent(this@PodcastPlayService, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } } ).apply { setPlayer(exoPlayer) setMediaSessionToken(mediaSession.sessionToken) } } override fun onDestroy() { super.onDestroy() exoPlayer.release() mediaSession.release() notificationManager.setPlayer(null) } // 按业务需求实现以下两个方法即可,基础播放功能不需要额外逻辑 override fun onGetRoot(clientPkg: String, uid: Int, hints: Bundle?): BrowserRoot { return BrowserRoot("root", null) } override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) { result.sendResult(mutableListOf()) } }
步骤2:Activity绑定Service获取播放器实例
同进程下通过 ServiceConnection 可以直接拿到 Service 的公开属性,不需要序列化传递对象:
class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private var playService: PodcastPlayService? = null private var mediaBrowser: MediaBrowserCompat? = null private val serviceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, binder: IBinder) { playService = (binder as PodcastPlayService.LocalBinder).getService() // 拿到全局唯一的ExoPlayer实例,直接绑定给PlayerView即可 binding.bottomSheetParent.playerView.player = playService?.exoPlayer } override fun onServiceDisconnected(name: ComponentName) { playService = null binding.bottomSheetParent.playerView.player = null } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) } override fun onStart() { super.onStart() // 连接MediaBrowserService获取MediaSession令牌 mediaBrowser = MediaBrowserCompat( this, ComponentName(this, PodcastPlayService::class.java), object : MediaBrowserCompat.ConnectionCallback() { override fun onConnected() { val token = mediaBrowser?.sessionToken ?: return MediaControllerCompat.setMediaController(this@MainActivity, MediaControllerCompat(this@MainActivity, token)) // 绑定服务获取播放器实例 bindService( Intent(this@MainActivity, PodcastPlayService::class.java), serviceConnection, Context.BIND_AUTO_CREATE ) } }, null ) mediaBrowser?.connect() } override fun onStop() { super.onStop() playService?.let { unbindService(serviceConnection) playService = null } mediaBrowser?.disconnect() } }
注意事项
- PlayerView 仅负责UI渲染和交互转发,只要绑定的是Service中托管的全局播放器实例,所有播放操作、状态同步、通知栏/锁屏控件更新都会自动生效,不需要额外做跨组件通信。
- 播放器生命周期完全和Service绑定,Activity销毁、退到后台都不会中断播放,符合后台音频播放的要求。
- 记得在 AndroidManifest.xml 中注册Service,添加前台服务权限、通知权限,Android 12及以上版本需要额外添加蓝牙媒体控制相关权限。
- 不需要照搬UAMP中基于MediaPlayer的实现逻辑,ExoPlayer提供的MediaSessionConnector、PlayerNotificationManager已经封装了所有状态同步、媒体事件转发的逻辑,代码量比原生MediaPlayer实现少很多。
内容的提问来源于stack exchange,提问作者krupa parekh
相关产品推荐
相关产品推荐

