[Android 从零到一] 权限管理:运行时权限与最佳实践

简介: Android 6.0起实行运行时权限模型,危险权限(如相机、定位)需动态申请,普通权限声明即用,特殊权限需跳转设置开启。Kotlin推荐使用`registerForActivityResult`安全请求,并妥善处理“不再询问”、版本差异(如Android 13通知权限)等坑点,兼顾隐私合规与用户体验。

背景

从 Android 6.0 (API 23) 开始,Google 引入了运行时权限模型。不再是在安装时"一键同意",而是需要开发者在运行时动态请求。Android 11+ 之后权限机制愈发严格,了解权限管理是每个 Android 开发者必备技能。

一、核心概念

权限分类

  • 普通权限(Normal):安装时默认授予,如 INTERNETACCESS_NETWORK_STATE,只需在 Manifest 中声明
  • 危险权限(Dangerous):涉及隐私数据,如相机、位置、联系人、存储,必须运行时动态请求
  • 特殊权限(Special):如 SYSTEM_ALERT_WINDOW(悬浮窗)、WRITE_SETTINGS,需引导用户到设置页手动开启

权限组

危险权限按功能分组,同一组内授权一个即全部获得:

  • CALENDAR — 日历
  • CAMERA — 相机
  • CONTACTS — 通讯录
  • LOCATION — 位置
  • STORAGE — 存储(Android 10 后逐步废弃)
  • NOTIFICATION — 通知(Android 13+ 成为运行时权限)

二、Kotlin 代码实战

声明权限

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

单个权限请求

class MainActivity : AppCompatActivity() {
    private val requestPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (isGranted) openCamera() else showPermissionDeniedDialog()
    }

    private fun checkCameraPermission() {
        when {
            ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                == PackageManager.PERMISSION_GRANTED -> openCamera()
            shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) ->
                showRationaleDialog { requestPermissionLauncher.launch(Manifest.permission.CAMERA) }
            else -> requestPermissionLauncher.launch(Manifest.permission.CAMERA)
        }
    }
}

多权限批量请求

private val multiPermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
    if (permissions.values.all { it }) {
        startFeature()
    } else {
        val denied = permissions.filter { !it.value }.keys
        Snackbar.make(binding.root, "被拒绝: $denied", Snackbar.LENGTH_LONG)
            .setAction("去设置") { openAppSettings() }.show()
    }
}

封装检查工具

object PermissionHelper {
    fun hasPermissions(context: Context, vararg permissions: String) =
        permissions.all {
            ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
        }
}

三、避坑指南

坑1:忘记处理"不再询问"

用户勾选"不再询问"后,shouldShowRequestPermissionRationale 返回 false。此时应引导跳转系统设置页:

private fun openAppSettings() {
    startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
        data = Uri.parse("package:$packageName")
    })
}

坑2:通知权限(Android 13+)

Android 13 将 POST_NOTIFICATIONS 列为运行时权限,默认拒绝。很多 App 在 13 设备上不弹通知就是这个原因。建议在合适的场景(如用户操作触发)请求,而非冷启动直接弹。

坑3:存储权限版本变化

  • Android 9 及以下:READ_EXTERNAL_STORAGE / WRITE_EXTERNAL_STORAGE
  • Android 10:引入 Scoped Storage,传统存储权限基本失效
  • Android 13:细化为 READ_MEDIA_IMAGESREAD_MEDIA_VIDEOREAD_MEDIA_AUDIO

坑4:registerForActivityResult 注册时机

必须在 onCreate 中(或 onStart 之前)注册,不能在按钮点击回调中懒加载创建,否则抛 IllegalStateException

四、总结

  • 使用 registerForActivityResult 替代废弃的 onRequestPermissionsResult
  • 危险权限必须运行时请求,普通权限声明即用
  • 处理好"不再询问"场景,引导用户到设置页
  • 关注版本差异做兼容
  • 请求前给用户上下文说明,降低拒绝率

权限设计本质是隐私保护与功能的平衡——宁可少要权限,也别让用户卸载你的 App。

相关文章
|
1月前
|
人工智能 监控 安全
Codex/Claude Code 如何配置自定义 API 端点教学
本文详解AI编程工具(Claude Code、OpenAI Codex、VS Code插件)接入企业网关的实践:厘清OpenAI/Anthropic协议差异,规范自定义Base URL与环境变量配置,提供跨平台标准化部署方案、连通性验证命令及密钥治理策略,助力企业统一管控、安全合规、高效运维。(239字)
|
1月前
|
存储 弹性计算 Java
阿里云服务器2核4G、4核8G、8核16G热门实例性能、价格对比与选型指南
2026年阿里云2核4G、4核8G、8核16G三大主流配置下,经济型e实例、通用算力型u1/u2i、计算型c9i及轻量应用服务器各有定位。轻量应用服务器低至38元/年,经济型e实例99元/年起,适合个人开发者与小微企业;通用算力型u1/u2i提供100%独享算力,u2i活动价3折起,4核8G仅1252元/年,性价比突出;计算型c9i搭载最新至强6处理器,单核算力提升20%,适合高并发、大数据等高性能场景。用户可根据预算与业务需求,从入门到旗舰按需选择。
|
存储 XML Java
Android 文件数据储存之内部储存 + 外部储存
简介:本文详细介绍了Android内部存储与外部存储的使用方法及核心原理。内部存储位于手机内存中,默认私有,适合存储SharedPreferences、SQLite数据库等重要数据,应用卸载后数据会被清除。外部存储包括公共文件和私有文件,支持SD卡或内部不可移除存储,需申请权限访问。文章通过代码示例展示了如何保存、读取、追加、删除文件以及将图片保存到系统相册的操作,帮助开发者理解存储机制并实现相关功能。
3076 2
|
安全 数据安全/隐私保护 智能硬件
|
存储 Go 开发工具
集中工作流命令行工具git-repo
git-repo 是一个用于集中工作流的命令行工具,可以与 Gerrit、AGit-Flow 兼容的服务器一起使用。它是用 Golang 编写的,无需进一步依赖即可轻松安装。它为多个存储库提供了一个易于使用的解决方案,首先由 Android repo 引入,它也可以与单个存储库一起使用。
|
XML 安全 Android开发
android Jetpack组件Navigation导航组件--组件间跳转、组件间传值功能实现
android Jetpack组件Navigation导航组件--组件间跳转、组件间传值功能实现
1474 0
android Jetpack组件Navigation导航组件--组件间跳转、组件间传值功能实现
|
安全 Android开发 数据安全/隐私保护
Android中的动态权限请求与最佳实践
【4月更文挑战第14天】 在现代安卓应用开发中,用户隐私和安全被赋予了前所未有的重要性。随着Android 6.0(API级别23)引入的运行时权限模型,开发者必须更加细致地处理权限请求,以确保应用功能的完整性同时不侵犯用户的隐私。本文将深入探讨如何在Android应用中实现动态权限请求,分析常见问题,并提供一系列最佳实践,以帮助开发者优雅地处理这一挑战。
881 5
|
8月前
|
SQL 安全 数据库
Jetpack Room 从入门到精通
Jetpack Room 是 Android 官方推荐的持久化库,基于 SQLite 提供类型安全、编译时验证的数据库操作。通过 @Entity、@Dao、@Database 三大组件,简化增删改查,支持协程、LiveData、Flow,实现响应式数据访问,助力构建高效、稳定的本地数据存储方案。
440 0
|
测试技术
关于RecycleView的getPosition(),getAdapterPosition()过时和getBindingAdapterPosition、getLayoutPosition..的总结
在App的开发过程中,常常会用的RecycleView,毕竟他可以横着来,竖着来,嵌套着来,而每个列表,我们通常都会写一个相对应的适配器(Adapter),里面包含了初始化,赋值,一些数据的监听,一些动作的监听等等。
PHP 中,使用 (int) 或者 intval() 函数可以将变量转换为整数类型,区别是什么?底层原理是什么?
PHP 中,使用 (int) 或者 intval() 函数可以将变量转换为整数类型,区别是什么?底层原理是什么?
1161 0