Android 10音乐播放器权限异常:READ_EXTERNAL_STORAGE无法播放音乐
音乐播放器权限问题解答
问题背景
开发的音乐播放器可加载外部存储音乐并播放,仅申请了READ_EXTERNAL_STORAGE权限,在Android 11/12模拟器正常运行,但Android 10真机上能加载音乐,播放时出现“permission denied”错误。
解答
1. 加载并播放手机中所有音乐需要哪些权限?
权限需求随Android版本不同有所区分:
- Android 10(API 29)及以下:仅需
READ_EXTERNAL_STORAGE权限,申请后可通过MediaStore读取并播放音乐。 - Android 11/12(API 30-31):可继续使用
READ_EXTERNAL_STORAGE,也可使用READ_MEDIA_AUDIO(该权限为Android 13引入的细分媒体权限,在11/12版本兼容)。 - Android 13+(API 33):必须使用
READ_MEDIA_AUDIO权限,READ_EXTERNAL_STORAGE不再对媒体文件生效。
2. 是否必须申请MANAGE_EXTERNAL_STORAGE权限?有无其他解决方法?
不需要申请MANAGE_EXTERNAL_STORAGE,该权限是针对全局访问外部存储的特殊权限,仅适用于文件管理器类应用,普通音乐播放器完全不需要。
核心解决方法:改用MediaStore的Uri而非文件路径播放
你的代码中通过MediaStore.Audio.Media.DATA获取文件路径后直接打开播放,在Android 10的Scoped Storage机制下,这种直接访问文件路径的方式会被拒绝权限。正确做法是:
- 加载音乐时,存储音乐对应的MediaStore Uri(而非本地文件路径)
- 播放时,通过
MediaPlayer.setDataSource(Context context, Uri uri)方法,或通过ContentResolver获取文件描述符来设置数据源。
3. 为何仅Android 10出现该错误,Android 11和12可正常运行?
这是Android Scoped Storage机制的版本兼容差异导致:
- Android 10(API 29):Scoped Storage严格生效,即使通过MediaStore查询到文件路径,直接访问该路径也没有权限,必须通过ContentResolver或MediaStore Uri来访问文件内容。
- Android 11+(API 30及以上):系统做了兼容优化,当通过MediaStore查询到文件路径后,系统会自动为该路径授予临时访问权限,因此直接用路径播放也能成功。
代码修改建议
1. 修改加载音乐的代码,存储MediaStore Uri而非文件路径
fun loadAllMusics() { val allSongs = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI val selection = MediaStore.Audio.Media.IS_MUSIC + "!=0" val projection = arrayOf( MediaStore.Audio.Media._ID, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.ALBUM_ID ) val cursor = contentResolver.query(allSongs, projection, selection, null, null) cursor?.use { if (it.moveToFirst()) { do { val songId = it.getLong(it.getColumnIndex(MediaStore.Audio.Media._ID)) val songUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, songId) val songAuthor = it.getString(it.getColumnIndex(MediaStore.Audio.Media.ARTIST)) val songName = it.getString(it.getColumnIndex(MediaStore.Audio.Media.TITLE)) val songDuration = it.getLong(it.getColumnIndex(MediaStore.Audio.Media.DURATION)) val albumID = it.getString(it.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID)) songItemsList.add( SongItem( songDuration, songName, songAuthor, songUri.toString(), // 存储Uri的字符串形式 getSongCover(albumID) ) ) } while (it.moveToNext()) } } }
2. 修改播放逻辑,使用Uri播放
// 播放时从SongItem中取出Uri字符串转为Uri对象 val songUri = Uri.parse(songItem.songURL) mediaPlayer.apply { reset() setDataSource(context, songUri) prepareAsync() // ...其他播放逻辑 }
或者使用文件描述符的方式(更安全):
contentResolver.openFileDescriptor(songUri, "r")?.use { fd -> mediaPlayer.apply { reset() setDataSource(fd.fileDescriptor) prepareAsync() // ...其他播放逻辑 } }
3. Manifest权限配置无需修改
你当前的Manifest中已添加READ_EXTERNAL_STORAGE和READ_MEDIA_AUDIO,系统会根据设备版本自动适配权限,无需额外调整。
内容的提问来源于stack exchange,提问作者Amirhussein
相关产品推荐
相关产品推荐

