API 29+下MediaStore音频数据获取失效,求替代实现方案
API 29+ 离线音乐播放器适配方案
核心问题
API 29(Android 10)开始引入分区存储(Scoped Storage),MediaStore.Audio.Media.DATA字段被废弃,无法直接获取音乐文件的绝对路径,这是原有代码失效的根本原因。官方推荐通过Content URI访问媒体资源,而非直接操作文件路径。
适配步骤与代码修改
1. 权限适配
- API 33及以上:申请
READ_MEDIA_AUDIO权限 - API 29~32:申请
READ_EXTERNAL_STORAGE权限
在AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" android:minSdkVersion="33" />
运行时需动态申请对应权限,确保权限通过后再执行媒体查询。
2. 修改媒体查询逻辑
不再获取DATA字段,改用_ID构建Content URI作为音乐资源的唯一标识,其他字段(标题、专辑、艺术家等)可正常获取。修改后的loadData方法如下:
public void loadData() { ContentResolver contentResolver = requireContext().getContentResolver(); Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; String selection = MediaStore.Audio.Media.IS_MUSIC + "!= 0"; String sortOrder = MediaStore.MediaColumns.TITLE + " ASC"; // 明确指定需要查询的列,减少冗余数据 String[] projection = { MediaStore.Audio.Media._ID, MediaStore.MediaColumns.TITLE, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.DURATION }; Cursor cursor = contentResolver.query(uri, projection, selection, null, sortOrder); if (cursor != null && cursor.getCount() > 0) { model = new ArrayList<>(); while (cursor.moveToNext()) { // 通过ID构建Content URI long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)); Uri contentUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id); String title = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.TITLE)); String album = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM)); String artist = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)); long duration = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)); int calculatedDuration = (int) (duration / 60000); // 转换为分钟(注意单位是毫秒) // 用Content URI替代原有的DATA路径存入模型 model.add(new MusicModel(contentUri.toString(), title, album, artist)); Log.e("test", "\n contentUri = " + contentUri + "\n title = " + title + "\n album = " + album + "\n artist = " + artist + "\n duration = " + calculatedDuration); } cursor.close(); } ArtistAdapter adapter = new ArtistAdapter(requireContext(), model); binding.rvArtist.setAdapter(adapter); binding.rvArtist.setHasFixedSize(true); }
3. 播放逻辑适配
播放音乐时,不能再使用文件路径,需通过ContentResolver获取文件描述符,示例(以MediaPlayer为例):
// 从MusicModel中取出Content URI字符串 Uri audioUri = Uri.parse(musicModel.getContentUri()); MediaPlayer mediaPlayer = new MediaPlayer(); try { ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(audioUri, "r"); if (pfd != null) { mediaPlayer.setDataSource(pfd.getFileDescriptor()); pfd.close(); mediaPlayer.prepare(); mediaPlayer.start(); } } catch (IOException e) { e.printStackTrace(); }
关键注意事项
- 不要尝试通过反射或非官方方式获取文件路径,分区存储下这类方法会失效或触发安全问题。
- 若需缓存封面等资源,可通过Content URI读取文件流后保存到应用私有目录,不要直接操作外部存储文件。
- 测试时需在API29+的设备或模拟器上验证,确保权限和逻辑正常。
内容的提问来源于stack exchange,提问作者efesydss
相关产品推荐
相关产品推荐

