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

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");
        }
}

问题根源分析

  1. Uri格式错误
    系统目录文件的路径Uri能被MediaPlayer识别是因为系统赋予了应用默认访问权限,但外部存储的文件路径Uri(file:///storage/...)无法被MediaPlayer.create()正确解析——Android推荐使用MediaStore提供的Content Uri(content://media/external/audio/media/xxx)访问外部媒体文件,而非直接使用文件路径。

  2. 权限适配不完整

  • Android 10(API 29)及以上,WRITE_EXTERNAL_STORAGE不再授予外部存储的直接访问权限,即使申请该权限也无法读取文件路径。
  • API 33(Android 13)及以上,需申请READ_MEDIA_AUDIO权限访问音频文件,原WRITE_EXTERNAL_STORAGE权限不再适用。
  • 旧版本中,仅申请WRITE_EXTERNAL_STORAGE虽包含读取权限,但显式申请READ_EXTERNAL_STORAGE更规范。
  1. 代码健壮性不足
    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.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 10:05:22