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

从 Android 6.0 (API 23) 开始,Google 引入了运行时权限模型,彻底改变了权限管理的游戏规则。不再是在安装时"一键同意",而是需要开发者在运行时动态请求。尤其是在 Android 11+ 之后,权限机制愈发严格——MANAGE_EXTERNAL_STORAGE、QUERY_ALL_PACKAGES 等新权限的加入,让权限管理成为每个 Android 开发者绕不开的话题。

一、核心概念

1.1 权限分类

Android 将权限分为三类:

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

1.2 权限组(Permission Groups)

危险权限按功能分组,同一组内的权限一旦授权一个,其余的也自动获得授权。常用的权限组包括:

  • CALENDAR —— 日历读写
  • CAMERA —— 相机
  • CONTACTS —— 通讯录
  • LOCATION —— 位置信息
  • STORAGE —— 存储读写(Android 10 后逐渐废弃,改用 Scoped Storage)
  • NOTIFICATION —— 通知权限(Android 13+ 变为运行时权限)
  • READ_MEDIA_* —— 媒体文件访问(Android 13+ 替代 READ_EXTERNAL_STORAGE)

二、Kotlin 代码实战

2.1 声明权限

<!-- 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" />

2.2 单个权限请求

使用 registerForActivityResult 是官方推荐的现代 API,替代了已废弃的 onRequestPermissionsResult:

class MainActivity : AppCompatActivity() {

    private val requestPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted: Boolean ->
        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)
            }
        }
    }
}

2.3 多权限请求

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

private fun requestPermissions() {
    multiPermissionLauncher.launch(
        arrayOf(
            Manifest.permission.CAMERA,
            Manifest.permission.ACCESS_FINE_LOCATION
        )
    )
}

2.4 封装权限工具类

推荐封装一个简洁的权限工具,适配 Fragment / Activity:

object PermissionHelper {

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

    fun hasDeniedAndNotAskAgain(
        activity: Activity, permission: String
    ): Boolean {
        return ContextCompat.checkSelfPermission(activity, permission)
            == PackageManager.PERMISSION_DENIED &&
               !activity.shouldShowRequestPermissionRationale(permission)
    }
}

三、避坑指南

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

当用户勾选"不再询问"并拒绝后,shouldShowRequestPermissionRationale 返回 false。此时应引导用户到应用设置页,而不是再次弹出系统弹窗(会直接返回拒绝)。

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

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

Android 13 将 POST_NOTIFICATIONS 列为运行时权限。很多 App 在 13 设备上发不出通知就是这个原因。记得在合适的时机请求,首次冷启动直接弹权限容易被打回。

坑3:存储权限在 Android 10+ 的变化

Android 10 引入 Scoped Storage,READ_EXTERNAL_STORAGE 基本失效。访问媒体用 MediaStore API,访问文件用 SAF(Storage Access Framework)。Android 13 又用 READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO 进一步细分。

坑4:registerForActivityResult 必须在生命周期早期注册

这个 API 必须在 onCreate(bundle) 之前(或其中)注册,不能在按钮点击后才创建,否则会抛出 IllegalStateException。

四、总结

Android 权限管理看似简单,实则随着版本迭代越来越复杂。核心要点:

  • 用 registerForActivityResult 替代老旧的 onRequestPermissionsResult
  • 危险权限必须运行时请求,普通权限只需 Manifest 声明
  • 处理好"不再询问"场景,引导用户跳转设置页
  • 关注 Android 版本差异(存储权限、通知权限)做兼容
  • 请求权限时给足上下文说明,别让用户一脸懵逼地点拒绝

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

posted @ 2026-06-17 11:10  天总会晴的  阅读(17)  评论(0)    收藏  举报