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

Android音乐播放器如何读取内部存储歌曲?权限适配方案

问题

我为App申请了READ_EXTERNAL_STORAGE权限,使其可读取SD卡中的歌曲。在模拟器上,由于存在SD卡存储,歌曲能够正常显示,但在物理设备上,因为歌曲存储在内部存储而非SD卡中,所以无法显示。请问如何获取内部存储中的歌曲?需要使用什么权限?


权限说明

  • 读取内部存储中的媒体文件,READ_EXTERNAL_STORAGE权限已经足够(Android 12及以下);Android 13及以上建议使用更细分的READ_MEDIA_AUDIO权限,同时保留旧权限做向下兼容。
  • 若App目标SDK为Android 10(API 29)及以上,Manifest中已添加的android:requestLegacyExternalStorage="true"可暂时绕过Scoped Storage限制,继续使用文件遍历方式。

快速修复:遍历公共音乐目录

你当前仅遍历了外部SD卡根目录,物理设备的内部存储媒体文件通常存放在公共音乐目录,修改displaySong方法,同时覆盖公共音乐目录和App私有存储目录:

修改MainActivity中的displaySong方法

public void displaySong() {
    ArrayList<File> mySongs = new ArrayList<>();
    // 遍历系统公共音乐目录(包含内部存储+SD卡的音乐文件夹)
    File publicMusicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
    if (publicMusicDir.exists()) {
        mySongs.addAll(findSong(publicMusicDir));
    }
    // 遍历App私有外部存储的音乐目录(可选,若有App自身保存的歌曲)
    File appPrivateMusicDir = getExternalFilesDir(Environment.DIRECTORY_MUSIC);
    if (appPrivateMusicDir != null && appPrivateMusicDir.exists()) {
        mySongs.addAll(findSong(appPrivateMusicDir));
    }

    ArrayList<String> items = new ArrayList<>();
    for (int i = 0; i < mySongs.size(); i++) {
        items.add(mySongs.get(i).getName().replace(".mp3", "").replace(".wav", ""));
    }

    ArrayAdapter<String> songAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, items);
    listView.setAdapter(songAdapter);
    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
            Intent intent = new Intent(MainActivity.this, PlaySong.class);
            intent.putExtra("position", i);
            intent.putExtra("songlist", mySongs);
            intent.putExtra("songname", items.get(i));
            startActivity(intent);
        }
    });
}

更稳定方案:使用MediaStore API(推荐)

直接遍历文件的方式在Android 10+会受Scoped Storage限制,改用MediaStore可可靠获取所有系统索引的音频文件,无需手动遍历目录:

修改MainActivity

package com.example.vocals;

import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import com.karumi.dexter.Dexter;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.single.PermissionListener;
import java.util.ArrayList;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.READ_MEDIA_AUDIO;

public class MainActivity extends AppCompatActivity {
    ListView listView;
    ArrayList<String> songNames = new ArrayList<>();
    ArrayList<String> songPaths = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = findViewById(R.id.songs);

        // 动态申请权限,适配Android 13+
        String permission = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU ? READ_MEDIA_AUDIO : READ_EXTERNAL_STORAGE;
        Dexter.withContext(this)
                .withPermission(permission)
                .withListener(new PermissionListener() {
                    @Override
                    public void onPermissionGranted(PermissionGrantedResponse permissionGrantedResponse) {
                        loadSongsFromMediaStore();
                    }

                    @Override
                    public void onPermissionDenied(com.karumi.dexter.listener.PermissionDeniedResponse permissionDeniedResponse) {}

                    @Override
                    public void onPermissionRationaleShouldBeShown(PermissionRequest permissionRequest, PermissionToken permissionToken) {
                        permissionToken.continuePermissionRequest();
                    }
                }).check();
    }

    private void loadSongsFromMediaStore() {
        String[] projection = {
                MediaStore.Audio.Media.DISPLAY_NAME,
                MediaStore.Audio.Media.DATA
        };

        String selection = MediaStore.Audio.Media.IS_MUSIC + " != 0";
        String sortOrder = MediaStore.Audio.Media.TITLE + " ASC";

        Cursor cursor = getContentResolver().query(
                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                projection,
                selection,
                null,
                sortOrder
        );

        if (cursor != null) {
            while (cursor.moveToNext()) {
                String songName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME));
                String songPath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));
                // 过滤mp3和wav文件
                if (songName.endsWith(".mp3") || songName.endsWith(".wav")) {
                    songNames.add(songName.replace(".mp3", "").replace(".wav", ""));
                    songPaths.add(songPath);
                }
            }
            cursor.close();
        } else {
            Log.d("info", "未找到可播放的音频文件");
        }

        ArrayAdapter<String> songAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, songNames);
        listView.setAdapter(songAdapter);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                Intent intent = new Intent(MainActivity.this, PlaySong.class);
                intent.putExtra("position", i);
                intent.putExtra("songPaths", songPaths);
                intent.putExtra("songname", songNames.get(i));
                startActivity(intent);
            }
        });
    }
}

修改PlaySong Activity

package com.example.vocals;

import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import java.util.ArrayList;

public class PlaySong extends AppCompatActivity {

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mp != null) {
            mp.stop();
            mp.release();
        }
        if (updateSeek != null) {
            updateSeek.interrupt();
        }
    }

    TextView txtsong;
    ImageView next, previous, pause;
    MediaPlayer mp;
    Thread updateSeek;
    int current_position = 0;
    int position;
    ArrayList<String> songPaths;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_play_song);
        txtsong = findViewById(R.id.txtsong);
        next = findViewById(R.id.next);
        previous = findViewById(R.id.previous);
        pause = findViewById(R.id.pause);
        SeekBar seekBar = findViewById(R.id.seekBar);

        Intent intent = getIntent();
        songPaths = intent.getStringArrayListExtra("songPaths");
        String txtcontent = intent.getStringExtra("songname");
        txtsong.setText(txtcontent);
        txtsong.setSelected(true);
        position = intent.getIntExtra("position", 0);

        playSong(position);

        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {}

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                if (mp != null && mp.isPlaying()) {
                    mp.seekTo(seekBar.getProgress());
                }
            }
        });

        updateSeek = new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    while (mp != null && current_position < mp.getDuration()) {
                        if (mp.isPlaying()) {
                            current_position = mp.getCurrentPosition();
                            seekBar.setProgress(current_position);
                        }
                        sleep(800);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        updateSeek.start();

        previous.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mp != null) {
                    mp.stop();
                    mp.release();
                }
                position = (position == 0) ? songPaths.size() - 1 : position - 1;
                playSong(position);
                pause.setImageResource(R.drawable.pause);
            }
        });

        pause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mp != null) {
                    if (mp.isPlaying()) {
                        mp.pause();
                        pause.setImageResource(R.drawable.play);
                    } else {
                        mp.start();
                        pause.setImageResource(R.drawable.pause);
                    }
                }
            }
        });

        next.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mp != null) {
                    mp.stop();
                    mp.release();
                }
                position = (position == songPaths.size() - 1) ? 0 : position + 1;
                playSong(position);
                pause.setImageResource(R.drawable.pause);
            }
        });
    }

    private void playSong(int pos) {
        String songPath = songPaths.get(pos);
        Uri uri = Uri.parse("file://" + songPath);
        mp = MediaPlayer.create(this, uri);
        mp.start();
        txtsong.setText(songPaths.get(pos).substring(songPaths.get(pos).lastIndexOf("/") + 1).replace(".mp3", "").replace(".wav", ""));
        SeekBar seekBar = findViewById(R.id.seekBar);
        seekBar.setMax(mp.getDuration());
        current_position = 0;
    }
}

修改Manifest权限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.vocals">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

    <application
        android:requestLegacyExternalStorage="true"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Vocals">
        <activity android:name=".PlaySong"
            android:label="Player"
            android:parentActivityName=".MainActivity"></activity>
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

内容的提问来源于stack exchange,提问作者thakur gupta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 15:00:54