Android 10及以上MediaSessionService音频无法播放问题排查
问题背景
我在安卓音频播放器项目中使用MediaSessionService作为前台服务播放音频。此前使用ExoPlayer搭配普通Service时,通过ContentResolver的getAudios(Activity activity)方法获取设备音频文件并封装进AudioModel对象,在Android 10上运行完全正常。但切换到MediaSessionService后,App在Android 8上可正常运行,Android 10中却无法播放音频。查看日志发现,播放器在缓冲后进入idle状态,推测是MediaItem存在异常。未使用MediaSession、MediaController等组件时,App在Android 8和10上均能正常工作,想知道Android 10是否有额外的权限要求?
一、权限相关排查
Android 10(API 29)引入了Scoped Storage机制,虽然你已经在Manifest中声明了对应权限,但仍需注意以下点:
- 你的权限请求逻辑中,Android 10属于API < 33的范围,请求的是
READ_EXTERNAL_STORAGE,这本身符合规则,但需要确认权限是否被用户正确授予。 - MediaSessionService作为后台服务,只要App前台已获得存储权限,后台即可正常访问媒体文件,Android 10没有额外的专属权限要求,但需确保权限状态在服务启动时已生效。
二、代码中的核心问题及修复方案
1. MediaSessionCallback的onAddMediaItems方法无效
你的onAddMediaItems方法中,使用peek操作MediaItem的构建器,但未替换原对象,导致修改后的MediaItem未被实际返回,MediaSession无法获取正确的播放源:
// 错误代码 @NonNull @Override public ListenableFuture<List<MediaItem>> onAddMediaItems(@NonNull MediaSession mediaSession, @NonNull MediaSession.ControllerInfo controller, @NonNull List<MediaItem> mediaItems) { Constants.LOG.mediaSessionLog("MediaSession > onAddMediaItems, size -> "+mediaItems.size()); List<MediaItem> updatedMediaItems = mediaItems.stream().peek( mediaItem -> mediaItem.buildUpon() .setUri(mediaItem.requestMetadata.mediaUri) .build()).collect(Collectors.toList()); return Futures.immediateFuture(updatedMediaItems); }
修复方案:使用map替代peek,生成新的MediaItem对象并返回:
// 修复后代码 @NonNull @Override public ListenableFuture<List<MediaItem>> onAddMediaItems(@NonNull MediaSession mediaSession, @NonNull MediaSession.ControllerInfo controller, @NonNull List<MediaItem> mediaItems) { Constants.LOG.mediaSessionLog("MediaSession > onAddMediaItems, size -> "+mediaItems.size()); List<MediaItem> updatedMediaItems = mediaItems.stream() .map(mediaItem -> mediaItem.buildUpon() .setUri(mediaItem.requestMetadata.mediaUri) .build()) .collect(Collectors.toList()); return Futures.immediateFuture(updatedMediaItems); }
2. AudioPlaybackService中Player实例冲突
在onStartCommand方法中,你重新调用PlayerCreator.getPlayer(getApplicationContext())替换了onCreate中初始化的Player实例,导致MediaSession关联的Player与实际操作的Player不一致,状态同步失败:
// 错误代码:重复创建Player,破坏MediaSession关联 @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); if (intent == null || intent.getAction() == null) return Service.START_NOT_STICKY; String action = intent.getAction(); this.player = PlayerCreator.getPlayer(getApplicationContext()); // 这行代码导致实例冲突 // ... 后续逻辑 }
修复方案:移除该行代码,直接使用onCreate中初始化的Player实例,或确保PlayerCreator.getPlayer()返回单例对象。
3. Android 10下媒体URI格式错误
在Android 10的Scoped Storage规则下,MediaStore.Audio.Media.DATA字段返回的文件路径无法直接通过Uri.parse()访问,需使用MediaStore的内容URI:
// 错误代码:使用DATA字段生成Uri while (cursor.moveToNext()) { AudioModel model = new AudioModel(Uri.parse(cursor.getString(dataCol)), cursor.getString(nameCol)); // ... 其他赋值操作 }
修复方案:通过媒体ID生成符合规则的内容URI:
// 修复后代码 while (cursor.moveToNext()) { long audioId = cursor.getLong(idCol); Uri audioUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, audioId); AudioModel model = new AudioModel(audioUri, cursor.getString(nameCol)); // ... 其他赋值操作 }
生成的URI格式为content://media/external/audio/media/xxx,ExoPlayer可通过ContentResolver正常读取媒体文件。
4. MediaController播放触发逻辑缺失
在setUpPlayer方法中,你仅调用了prepare()但未触发播放,需确保prepare完成后调用play():
// 修复后代码 this.mediaController.setMediaItems(mediaItemList); this.mediaController.prepare(); this.mediaController.play(); // 添加播放触发 this.binding.progressBarAudioFileLoad.setVisibility(View.GONE);
三、总结修复步骤
- 修复MediaSessionCallback的
onAddMediaItems方法,使用map生成有效MediaItem。 - 移除AudioPlaybackService中重复创建Player的代码,保持MediaSession与Player的关联一致性。
- 修改
getAudios方法,使用ContentUris生成符合Scoped Storage规则的媒体URI。 - 确认Android 10设备上
READ_EXTERNAL_STORAGE权限已被正确授予。 - 在
setUpPlayer中添加播放触发逻辑,确保prepare后调用play()。
内容的提问来源于stack exchange,提问作者Ashis Kayal

