如何通过MediaSessionService实现Glance Widget音频播放
解决方案
一、实现从Widget触发音频播放
Widget属于跨进程组件,无法直接调用应用内播放器逻辑,需通过启动MediaSessionService并传递操作指令来实现播放控制:
- 完善Widget点击回调逻辑
在AudiotWidgetView的onSoundPlayStopClicked中,发送Intent到MediaSessionService,携带音频ID/URL及操作类型(播放/停止):
onSoundPlayStopClicked = { soundUrl -> val intent = Intent(context, YourMediaSessionService::class.java).apply { action = if (playingSoundUrl == soundUrl) { ACTION_STOP_PLAYBACK } else { ACTION_START_PLAYBACK } putExtra(EXTRA_SOUND_URL, soundUrl) } // Android 12+需使用startForegroundService启动前台服务 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { context.startForegroundService(intent) } else { context.startService(intent) } }
- 在MediaSessionService中处理Intent
重写onStartCommand方法,解析Intent并调用SoundServiceHandler的播放/停止方法:
@HiltService class YourMediaSessionService : MediaSessionService() { @Inject lateinit var soundServiceHandler: SoundServiceHandler private lateinit var mediaSession: MediaSession override fun onCreate() { super.onCreate() // 初始化前台服务通知,确保服务存活 createNotificationChannel(this) val notification = buildPlaybackNotification() startForeground(PLAYBACK_NOTIFICATION_ID, notification) mediaSession = soundServiceHandler.mediaSession } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { intent?.let { when (it.action) { ACTION_START_PLAYBACK -> { val soundUrl = it.getStringExtra(EXTRA_SOUND_URL) soundUrl?.let { url -> soundServiceHandler.playSound(url) // 调用已实现的播放方法 } } ACTION_STOP_PLAYBACK -> { soundServiceHandler.stopPlayback() // 调用已实现的停止方法 } } } return START_STICKY // 服务被杀死后尝试重启 } private fun buildPlaybackNotification(): Notification { return NotificationCompat.Builder(this, PLAYBACK_CHANNEL_ID) .setContentTitle("音频播放中") .setSmallIcon(R.drawable.ic_audio_play) .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE) .build() } override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession { return mediaSession } companion object { const val ACTION_START_PLAYBACK = "com.your.package.ACTION_START_PLAYBACK" const val ACTION_STOP_PLAYBACK = "com.your.package.ACTION_STOP_PLAYBACK" const val EXTRA_SOUND_URL = "EXTRA_SOUND_URL" const val PLAYBACK_NOTIFICATION_ID = 1001 const val PLAYBACK_CHANNEL_ID = "AUDIO_PLAYBACK_CHANNEL" } }
- 同步应用UI状态
在Compose页面中,通过collectAsStateWithLifecycle监听SoundServiceHandler的状态流,实时更新播放UI:
// 应用内Compose页面 val serviceState by soundServiceHandler.serviceState.collectAsStateWithLifecycle() when (serviceState) { is PlayerState.Playing -> { // 渲染播放中UI(如暂停按钮) } PlayerState.Stopped, PlayerState.Initial -> { // 渲染停止/初始UI(如播放按钮) } }
二、解决应用被后台清除后Widget无法更新的问题
核心原因是MediaSessionService未以前台服务运行,导致应用被杀死后服务被系统回收,无法触发Widget更新。需做以下调整:
- 确保Service为前台服务
在MediaSessionService的onCreate中调用startForeground,并提前创建通知渠道(Android O+要求):
// 全局创建通知渠道(可在Application onCreate中调用) fun createNotificationChannel(context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( PLAYBACK_CHANNEL_ID, "音频播放", NotificationManager.IMPORTANCE_LOW ).apply { description = "音频播放前台服务通知" } val notificationManager = context.getSystemService(NotificationManager::class.java) notificationManager.createNotificationChannel(channel) } }
- 正确配置Hilt与Manifest
确保MediaSessionService使用@HiltService注解,并在Manifest中注册:
<service android:name=".service.YourMediaSessionService" android:exported="true" android:foregroundServiceType="mediaPlayback"> <intent-filter> <action android:name="android.media.browse.MediaBrowserService" /> </intent-filter> </service>
- 优化Widget更新任务
使用WorkManager的唯一任务策略,避免重复更新任务,同时确保任务在后台可执行:
// 替换原WidgetWorker.enqueue方法 fun enqueue(context: Context, soundUrl: String?) { val inputData = workDataOf(PLAYING_SOUND_URL_KEY to soundUrl) val updateRequest = OneTimeWorkRequestBuilder<WidgetWorker>() .setInputData(inputData) .setConstraints(Constraints.Builder() .setRequiredNetworkType(NetworkType.NOT_REQUIRED) .build()) .build() WorkManager.getInstance(context) .enqueueUniqueWork( "UpdateAudioWidget", ExistingWorkPolicy.REPLACE, updateRequest ) }
关键注意事项
- Widget通过DataStore监听播放状态变化,需确保
playingSoundUrlStream能正确触发Glance重组。 - 前台服务通知需符合媒体播放规范,否则系统可能限制服务存活时间。
- 从Widget触发播放后,需同时更新DataStore与
SoundServiceHandler的状态流,保证应用UI与Widget状态同步。
内容的提问来源于stack exchange,提问作者Пенка Пшеничная
相关产品推荐
相关产品推荐

