Android MediaPlayer播放外部存储文件触发空指针异常求助
Android MediaPlayer播放外部存储音频触发空指针异常问题分析
问题现象
播放系统目录下的音频文件时运行正常:
uri = Uri.parse("/data/hw_init/hw_product/media/Pre-loaded/Music/Beijing_Bass.mp3"); MediaPlayer.create(getApplicationContext(), uri).start();
但播放外部存储(/storage/emulated/0/...)下的音频文件时,MediaPlayer.create()返回null,触发空指针异常。
现有代码与配置
权限申请代码(MainActivity)
// 申请读写外部存储权限 private void permission(){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); } else { Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show(); musicFiles = getAllAudioFromDevice(this); } } } // 权限申请回调,获取音频文件 @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 1) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { permission(); } else { requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); } } }
Manifest权限配置
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
音频文件获取代码
public static ArrayList<MusicFiles> getAllAudioFromDevice(final MainActivity mainActivity) { ArrayList<MusicFiles> tempAudioList = new ArrayList<>(); Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; String[] projection = { MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.DATA, // 存储文件路径 MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media._ID }; Cursor cursor = mainActivity.getContentResolver().query(uri, projection, null, null, null); if (cursor != null) { while (cursor.moveToNext()) { String album = cursor.getString(0); String artist = cursor.getString(1); String duration = cursor.getString(2); String path = cursor.getString(3); String displayName = cursor.getString(4); String title = cursor.getString(5); String id = cursor.getString(6); MusicFiles musicFilesTemp = new MusicFiles(path, title, artist, album, duration, displayName, id); Log.e("Music","Path : " + path +" Album : " + album ); tempAudioList.add(musicFilesTemp); } cursor.close(); } Log.e("Music","search complete"); return tempAudioList; }
MediaPlayer启动代码
private void getIntentMethod() { position = getIntent().getIntExtra("position", -1); listSongs = musicFiles; if (listSongs != null) { playPauseBtn.setImageResource(R.drawable.ic_pause); uri = Uri.parse(listSongs.get(position).getPath()); } if (mediaPlayer != null) { mediaPlayer.stop(); mediaPlayer.release(); Log.e("Music","Media Player released"); } mediaPlayer = MediaPlayer.create(getApplicationContext(), uri); Log.e("Music",uri.toString()); if (mediaPlayer != null) { mediaPlayer.start(); seekBar.setMax(mediaPlayer.getDuration() / 1000); duration_total.setText(formattedTime(mediaPlayer.getDuration() / 1000)); song_name.setText(listSongs.get(position).getTitle()); artist_name.setText(listSongs.get(position).getArtist()); metaData(uri); } else { Log.e("Media Player", "Media Player is null"); } }
问题根源分析
Uri格式错误
系统目录文件的路径Uri能被MediaPlayer识别是因为系统赋予了应用默认访问权限,但外部存储的文件路径Uri(file:///storage/...)无法被MediaPlayer.create()正确解析——Android推荐使用MediaStore提供的Content Uri(content://media/external/audio/media/xxx)访问外部媒体文件,而非直接使用文件路径。权限适配不完整
- Android 10(API 29)及以上,
WRITE_EXTERNAL_STORAGE不再授予外部存储的直接访问权限,即使申请该权限也无法读取文件路径。 - API 33(Android 13)及以上,需申请
READ_MEDIA_AUDIO权限访问音频文件,原WRITE_EXTERNAL_STORAGE权限不再适用。 - 旧版本中,仅申请
WRITE_EXTERNAL_STORAGE虽包含读取权限,但显式申请READ_EXTERNAL_STORAGE更规范。
- 代码健壮性不足
getIntentMethod未对position有效性(如是否超出列表范围)、uri是否为空做判断,异常场景下会直接导致MediaPlayer.create()返回null,触发NPE。
解决方案建议
1. 使用MediaStore Content Uri替代文件路径
修改getAllAudioFromDevice方法,存储MediaStore生成的Content Uri而非文件路径:
public static ArrayList<MusicFiles> getAllAudioFromDevice(final MainActivity mainActivity) { ArrayList<MusicFiles> tempAudioList = new ArrayList<>(); Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; String[] projection = { MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media._ID }; Cursor cursor = mainActivity.getContentResolver().query(uri, projection, null, null, null); if (cursor != null) { while (cursor.moveToNext()) { String album = cursor.getString(0); String artist = cursor.getString(1); String duration = cursor.getString(2); String displayName = cursor.getString(3); String title = cursor.getString(4); String id = cursor.getString(5); // 生成Content Uri Uri contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.buildUpon() .appendPath(id) .build(); // 修改MusicFiles构造方法,支持存储Content Uri字符串 MusicFiles musicFilesTemp = new MusicFiles(contentUri.toString(), title, artist, album, duration, displayName, id); Log.e("Music","Content Uri : " + contentUri +" Album : " + album ); tempAudioList.add(musicFilesTemp); } cursor.close(); } Log.e("Music","search complete"); return tempAudioList; }
播放时直接使用存储的Content Uri:
// 在getIntentMethod中替换Uri解析逻辑 uri = Uri.parse(listSongs.get(position).getContentUri()); // 假设MusicFiles新增getContentUri()方法返回存储的Uri字符串
2. 适配不同Android版本的权限
修改权限申请代码,针对不同API版本申请对应权限:
private void requestAudioPermissions() { String[] permissions; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // Android 13+ 申请音频读取权限 permissions = new String[]{Manifest.permission.READ_MEDIA_AUDIO}; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Android 6-12 申请存储读写权限 permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}; } else { // 低于Android 6无需动态申请 permissions = new String[]{}; } if (permissions.length > 0) { requestPermissions(permissions, 1); } else { musicFiles = getAllAudioFromDevice(this); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 1) { boolean allGranted = true; for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { allGranted = false; break; } } if (allGranted) { musicFiles = getAllAudioFromDevice(this); } else { Toast.makeText(this, "需要权限才能加载音频文件", Toast.LENGTH_SHORT).show(); // 可选:引导用户到应用设置开启权限 // Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + getPackageName())); // startActivity(intent); } } }
针对Android 10-12,若需兼容旧文件路径访问方式,可在Manifest的<application>标签中添加:
android:requestLegacyExternalStorage="true"
注意:该属性为临时兼容方案,Android 14及以上不再支持。
3. 增强代码健壮性
在getIntentMethod中增加空判断与边界检查:
private void getIntentMethod() { position = getIntent().getIntExtra("position", -1); listSongs = musicFiles; // 检查列表有效性与position合法性 if (listSongs == null || position < 0 || position >= listSongs.size()) { Toast.makeText(this, "无法找到指定音频文件", Toast.LENGTH_SHORT).show(); return; } playPauseBtn.setImageResource(R.drawable.ic_pause); uri = Uri.parse(listSongs.get(position).getContentUri()); // 释放旧的MediaPlayer if (mediaPlayer != null) { mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; Log.e("Music","Media Player released"); } mediaPlayer = MediaPlayer.create(getApplicationContext(), uri); Log.e("Music",uri.toString()); if (mediaPlayer != null) { mediaPlayer.start(); seekBar.setMax(mediaPlayer.getDuration() / 1000); duration_total.setText(formattedTime(mediaPlayer.getDuration() / 1000)); song_name.setText(listSongs.get(position).getTitle()); artist_name.setText(listSongs.get(position).getArtist()); metaData(uri); } else { Log.e("Media Player", "Media Player初始化失败"); Toast.makeText(this, "无法加载选中的音频文件", Toast.LENGTH_SHORT).show(); } }
内容的提问来源于stack exchange,提问作者M. D.
相关产品推荐
相关产品推荐

