You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

音乐播放器读取存储权限问题:启动崩溃后才触发权限请求

解决权限申请前崩溃的问题

嘿,这个问题我太熟了——你踩了Android权限申请里最常见的异步坑!核心问题是:权限请求是异步操作,但你在调用requestPermissions后立刻就去执行读取音频文件的逻辑,这时候权限还没被用户授予,直接访问存储就会触发SecurityException导致崩溃,而系统的权限弹窗反而在崩溃之后才来得及弹出来。

下面是具体的修复方案,推荐用官方最新的API,也给你留了旧方式的兼容方案:

方案一:使用官方推荐的ActivityResultAPI(AndroidX)

这个方式更简洁,还能避免重写回调的繁琐,是目前官方主推的写法:

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.Manifest;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    // 定义权限请求的Launcher
    private ActivityResultLauncher<String[]> requestPermissionLauncher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化权限请求回调
        requestPermissionLauncher = registerForActivityResult(
                new ActivityResultContracts.RequestMultiplePermissions(),
                permissions -> {
                    // 根据Android版本判断权限是否授予
                    boolean hasAudioPermission;
                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
                        hasAudioPermission = permissions.getOrDefault(Manifest.permission.READ_MEDIA_AUDIO, false);
                    } else {
                        hasAudioPermission = permissions.getOrDefault(Manifest.permission.READ_EXTERNAL_STORAGE, false);
                    }

                    if (hasAudioPermission) {
                        // 权限到手,现在安全执行读取音频的逻辑
                        loadAudioFiles();
                    } else {
                        Toast.makeText(this, "需要音频权限才能加载音乐", Toast.LENGTH_SHORT).show();
                    }
                }
        );

        // 启动第一步:检查权限,没有就申请
        checkAndRequestPermissions();
    }

    private void checkAndRequestPermissions() {
        boolean needRequest = false;
        String[] targetPermissions;

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
            // Android 13+ 用细分的音频权限
            targetPermissions = new String[]{Manifest.permission.READ_MEDIA_AUDIO};
            needRequest = checkSelfPermission(Manifest.permission.READ_MEDIA_AUDIO) != android.content.pm.PackageManager.PERMISSION_GRANTED;
        } else {
            // Android 12及以下用存储权限
            targetPermissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
            needRequest = checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != android.content.pm.PackageManager.PERMISSION_GRANTED;
        }

        if (needRequest) {
            // 发起权限请求
            requestPermissionLauncher.launch(targetPermissions);
        } else {
            // 已有权限,直接加载音频
            loadAudioFiles();
        }
    }

    // 把你的音频读取逻辑单独放在这里
    private void loadAudioFiles() {
        // 这里写列出所有音频文件的代码,比如用ContentResolver查询媒体库
        // ...
    }
}

方案二:使用旧的requestPermissions(兼容老项目)

如果你的项目还没迁移到AndroidX,或者习惯用传统写法,可以这样做:

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

public class MainActivity extends AppCompatActivity {
    private static final int REQUEST_AUDIO_PERMISSION = 1001;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 先检查权限再干活
        checkAndRequestPermissions();
    }

    private void checkAndRequestPermissions() {
        boolean hasPermission;
        String[] targetPermissions;

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
            targetPermissions = new String[]{Manifest.permission.READ_MEDIA_AUDIO};
            hasPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_AUDIO) == PackageManager.PERMISSION_GRANTED;
        } else {
            targetPermissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
            hasPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
        }

        if (!hasPermission) {
            ActivityCompat.requestPermissions(this, targetPermissions, REQUEST_AUDIO_PERMISSION);
        } else {
            loadAudioFiles();
        }
    }

    // 重写权限请求结果回调
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_AUDIO_PERMISSION) {
            boolean isGranted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
            if (isGranted) {
                loadAudioFiles();
            } else {
                Toast.makeText(this, "权限被拒绝,无法加载音乐", Toast.LENGTH_SHORT).show();
            }
        }
    }

    private void loadAudioFiles() {
        // 你的音频读取逻辑
        // ...
    }
}

关键注意点

  • 异步逻辑别着急:永远不要在调用权限请求后立刻执行需要权限的代码,必须等回调确认权限已授予后再动手。
  • 版本适配要到位:Android 13+把媒体权限拆分成了音频、图片、视频三个单独权限,读取音频要用READ_MEDIA_AUDIO,别再用旧的存储权限了。
  • 启动流程要调整:别在onCreate/onStart里直接跑音频读取逻辑,必须先走完「权限检查→请求→确认」的流程。

这样修改后,首次启动会先弹出权限请求弹窗,用户授予后才会执行读取音频的逻辑,再也不会出现崩溃后才弹权限的尴尬情况了。

内容的提问来源于stack exchange,提问作者Michael Florian

火山引擎 最新活动