获取本地存储歌曲流派时应用崩溃,求解决方案
解决Android获取设备歌曲流派时应用崩溃的问题
我知道该问题此前已有提问,但尚未找到解决方案。我尝试获取设备存储中歌曲的流派,但运行以下代码时应用崩溃:
public void getMusic() { ContentResolver contentResolver = getContentResolver(); Uri songUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; Cursor songCursor = contentResolver.query(songUri, null, null, null, null); if (songCursor != null && songCursor.moveToFirst()) { int songTitle = songCursor.getColumnIndex(Media... } }
我来帮你排查下这个崩溃问题,结合Android媒体存储的机制,你的代码大概率是因为权限缺失、空值未处理、Cursor使用不规范这几个原因导致的,下面是具体的修复方案:
一、先明确崩溃的核心原因
- 权限未申请或未校验:Android 12及以下需要
READ_EXTERNAL_STORAGE权限,Android 13+则需要READ_MEDIA_AUDIO权限,没申请直接访问媒体库会抛出SecurityException导致崩溃。 - Cursor列处理不当:你代码里用
null作为query的投影参数(即获取所有列),但如果流派字段为空或者索引获取错误,直接取值会触发异常;而且你代码里的Media...没写完,大概率是要获取MediaStore.Audio.Media.GENRE字段,这个字段本身可能为空,必须做空值判断。 - Cursor未正确关闭:如果Cursor使用后不关闭,会导致内存泄漏,极端情况也会引发崩溃。
二、修复后的完整代码
// 定义需要获取的媒体列,避免获取不必要的数据 private static final String[] MUSIC_PROJECTION = { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.GENRE // 目标流派字段 }; public void getMusic() { // 先校验权限,不同Android版本权限不同 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_AUDIO) != PackageManager.PERMISSION_GRANTED) { // 申请Android13+的音频权限 registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) loadMusicData(); else Toast.makeText(this, "需要音频权限才能获取歌曲信息", Toast.LENGTH_SHORT).show(); }).launch(Manifest.permission.READ_MEDIA_AUDIO); return; } } else { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // 申请旧版本的存储权限 registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) loadMusicData(); else Toast.makeText(this, "需要存储权限才能获取歌曲信息", Toast.LENGTH_SHORT).show(); }).launch(Manifest.permission.READ_EXTERNAL_STORAGE); return; } } // 权限已通过,加载音乐数据 loadMusicData(); } private void loadMusicData() { ContentResolver contentResolver = getContentResolver(); Uri songUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; // 使用指定投影查询,减少资源占用 Cursor songCursor = contentResolver.query(songUri, MUSIC_PROJECTION, null, null, null); if (songCursor != null) { try { // 获取列索引,避免硬编码位置 int genreColIndex = songCursor.getColumnIndex(MediaStore.Audio.Media.GENRE); int titleColIndex = songCursor.getColumnIndex(MediaStore.Audio.Media.TITLE); while (songCursor.moveToNext()) { // 处理流派字段为空的情况 String genre = songCursor.isNull(genreColIndex) ? "未知流派" : songCursor.getString(genreColIndex); String songTitle = songCursor.getString(titleColIndex); // 这里可以把数据加入列表或做其他处理 Log.d("MusicInfo", "歌曲:" + songTitle + " | 流派:" + genre); } } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, "获取歌曲信息失败", Toast.LENGTH_SHORT).show(); } finally { // 务必关闭Cursor,释放系统资源 songCursor.close(); } } }
三、额外注意事项
- 流派字段的局限性:部分音乐文件可能没有嵌入流派元数据,或者MediaStore未正确索引,此时
GENRE字段会为空,一定要用isNull()做判断。 - 更精准的流派获取:如果MediaStore的流派信息不全,你可以直接解析音乐文件的元数据:
MediaMetadataRetriever retriever = new MediaMetadataRetriever(); retriever.setDataSource("你的音乐文件路径"); String genre = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE); retriever.release(); // 记得释放资源 - 权限申请的时机:最好在用户触发获取音乐的操作时再申请权限,避免启动时就弹出权限弹窗影响体验。
内容的提问来源于stack exchange,提问作者Pointyhat
相关产品推荐
相关产品推荐

