基于不同API Level的Android Kotlin应用音频文件权限处理问题
Android Kotlin音频文件读取权限请求异常修复方案
问题概述
在Android Kotlin应用中配置了适配不同API级别的音频读取权限(API29/30用READ_EXTERNAL_STORAGE,API31+用READ_MEDIA_AUDIO),使用EasyPermissions库处理运行时权限时,同时请求两个权限会导致:即使授权成功,仍触发onPermissionsDenied方法,无法正常跳转至目标页面。
问题原因
- Android从API31(Android 12)开始,
READ_EXTERNAL_STORAGE权限被READ_MEDIA_AUDIO等细分媒体权限替代,系统会自动忽略API31+设备上对READ_EXTERNAL_STORAGE的权限请求,导致该权限被标记为"未授予" - EasyPermissions库的权限检查逻辑是:只要请求的权限中有一个未被授予,就会触发
onPermissionsDenied回调,即使另一个权限已正常授权
修复方案
核心思路是根据当前设备的API级别,动态选择对应权限进行请求和校验,避免同时请求互斥的权限。
1. 保持Manifest权限配置不变
AndroidManifest.xml中的权限配置无需修改,保留原有两个权限声明即可:
<!-- READ_EXTERNAL_STORAGE:: Works in API LEVEL 29, 30 but not above --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- READ_MEDIA_AUDIO:: Works in API LEVEL 31 and above --> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
2. 修改MainActivity.kt代码
调整权限判断、请求逻辑,以及权限回调的处理:
package com.playmakers.permissiontest import android.content.Intent import android.os.Build import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.Button import android.widget.Toast import com.vmadalin.easypermissions.EasyPermissions import com.vmadalin.easypermissions.dialogs.SettingsDialog class MainActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks { companion object{ const val PERMISSION_REQUEST_CODE = 1 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val button = findViewById<Button>(R.id.button) button.setOnClickListener { if(hasStoragePermission()){ startActivity(Intent(this, SecondActivity::class.java)) } else { requestStoragePermission() } } } // 根据API级别判断对应权限是否已授予 private fun hasStoragePermission(): Boolean { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { EasyPermissions.hasPermissions(this, android.Manifest.permission.READ_MEDIA_AUDIO) } else { EasyPermissions.hasPermissions(this, android.Manifest.permission.READ_EXTERNAL_STORAGE) } } // 根据API级别动态请求对应权限 private fun requestStoragePermission(){ val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { arrayOf(android.Manifest.permission.READ_MEDIA_AUDIO) } else { arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE) } EasyPermissions.requestPermissions( this, "This app needs access to your storage to read files.", PERMISSION_REQUEST_CODE, *permissions ) } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) } override fun onPermissionsDenied(requestCode: Int, perms: List<String>) { if(EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { SettingsDialog.Builder(this).build().show() } else { // 临时拒绝权限,提示用户需要授权才能继续 Toast.makeText(this, "Permission required to access audio files", Toast.LENGTH_SHORT).show() } } override fun onPermissionsGranted(requestCode: Int, perms: List<String>) { Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show() // 权限授予成功后跳转页面 startActivity(Intent(this, SecondActivity::class.java)) } }
关键修改点说明
- 移除了
@RequiresApi(Build.VERSION_CODES.TIRAMISU)注解,代码通过API级别分支处理实现全版本兼容 hasStoragePermission方法:根据API级别判断对应权限是否已授予,不再同时检查两个权限requestStoragePermission方法:动态生成对应API级别的权限数组,只请求当前系统需要的权限onPermissionsGranted回调中添加页面跳转逻辑,确保授权成功后直接进入目标页面- 优化
onPermissionsDenied逻辑,区分临时拒绝和永久拒绝的提示
验证效果
- API29/30设备:请求
READ_EXTERNAL_STORAGE权限,授权后正常跳转 - API31+设备:请求
READ_MEDIA_AUDIO权限,授权后正常跳转 - 无论哪个API级别,授权成功后不会触发
onPermissionsDenied回调
内容的提问来源于stack exchange,提问作者Mehedi Hasan
相关产品推荐
相关产品推荐

