音乐播放器读取存储权限问题:启动崩溃后才触发权限请求
解决权限申请前崩溃的问题
嘿,这个问题我太熟了——你踩了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




