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

如何在Jetpack Compose中正确申请READ_EXTERNAL_STORAGE运行时权限?

Android 运行时权限申请规范(以外部存储读取权限为例)

一、权限声明与版本适配

首先必须在AndroidManifest.xml中根据目标SDK版本正确声明权限:

<!-- Android 12 及以下版本 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<!-- Android 13+ 版本,推荐使用拆分后的媒体权限(替代旧的READ_EXTERNAL_STORAGE) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

Android 13开始,READ_EXTERNAL_STORAGE被拆分为三个细化的媒体权限,若目标SDK≥33仍仅声明旧权限,系统可能直接拒绝请求而不触发弹窗。

二、规范的权限请求流程

权限请求必须包含前置状态检查、权限合理性解释、拒绝后的兜底处理,以下是适配你的Compose项目的修正代码:

class MainActivity : ComponentActivity() {
    private val requestPermissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
            if (isGranted) {
                Log.e("activity", "权限已授予")
            } else {
                // 判断用户是否勾选了"不再询问"
                val permission = getTargetPermission()
                if (!shouldShowRequestPermissionRationale(permission)) {
                    // 用户拒绝且禁止再次弹窗,引导至应用设置页手动开启
                    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
                        data = Uri.fromParts("package", packageName, null)
                    }
                    startActivity(intent)
                } else {
                    Log.e("activity", "权限被拒绝")
                }
            }
        }

    private fun getTargetPermission(): String {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // Android 13+ 以读取图片为例,按需替换为视频/音频权限
            Manifest.permission.READ_MEDIA_IMAGES
        } else {
            Manifest.permission.READ_EXTERNAL_STORAGE
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Greeting("Android", {
                        val permission = getTargetPermission()
                        when {
                            // 权限已授予,直接执行业务逻辑
                            ContextCompat.checkSelfPermission(this@MainActivity, permission) == PackageManager.PERMISSION_GRANTED -> {
                                Log.e("activity", "权限已存在,可直接操作")
                            }
                            // 需要向用户解释权限用途(比如弹出提示框说明为什么需要访问相册)
                            shouldShowRequestPermissionRationale(permission) -> {
                                // 这里可添加Composable弹窗告知用户权限必要性,再发起请求
                                Log.e("activity", "请先向用户解释权限用途")
                                requestPermissionLauncher.launch(permission)
                            }
                            // 首次请求或用户未勾选"不再询问",直接发起系统弹窗请求
                            else -> {
                                requestPermissionLauncher.launch(permission)
                            }
                        }
                    })
                }
            }
        }
    }
}

@Composable
fun Greeting(name: String, callback: () -> Unit, modifier: Modifier = Modifier) {
    Box {
        Button(onClick = callback) {
            Text("申请权限")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview(onClick: () -> Unit = {}) {
    MyApplicationTheme {
        Greeting("Android", onClick)
    }
}

三、关键注意事项

  • 权限请求触发时机:必须通过用户主动交互(如按钮点击)发起请求,禁止在onCreate等生命周期方法中自动请求,系统会拦截非用户触发的权限申请。
  • Android 13+权限映射:若仍使用READ_EXTERNAL_STORAGE,系统会自动映射为对应的媒体权限,但如果用户之前拒绝过该权限,会直接返回拒绝而不弹窗,建议直接使用新的媒体权限。
  • 拒绝后的处理逻辑:当用户拒绝权限并勾选"不再询问"时,shouldShowRequestPermissionRationale会返回false,此时无法再通过系统弹窗申请,必须引导用户到应用设置页手动开启。
  • 替代方案推荐:若仅需访问特定文件,优先使用存储访问框架(SAF),通过ACTION_OPEN_DOCUMENT等Intent让用户选择文件,无需申请运行时权限,这是Android 10+官方推荐的存储访问方式。

四、你的代码问题排查方向

  1. 检查AndroidManifest.xml是否正确声明对应权限,尤其是目标SDK≥33时是否添加了新的媒体权限;
  2. 确认测试设备的Android版本,若为Android 13+,直接申请READ_EXTERNAL_STORAGE可能因权限映射规则导致无弹窗;
  3. 检查应用设置中是否已拒绝该权限并勾选"不再询问",若已设置,需先手动开启权限或在代码中引导跳转设置页。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 22:06:04