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

基于不同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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 06:52:47