You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 20:05:54