Android Academy · 课时

权限与运行时请求

使用 ActivityResultContracts 在运行时声明并请求危险权限,显示权限说明对话框,并处理永久拒绝。

第 7 / 7 课13 个步骤

权限与运行时请求 是 CoddyKit 上的免费 Android Academy 课时。 这是第 7 节课,共 7 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Android Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Android Academy 课程共包含 7 节课。

Android 权限概述

Android 应用必须声明所需的设备功能。权限用于保护用户隐私:

  • 普通权限 — 风险较低,会自动授予(INTERNET、VIBRATE)
  • 危险权限 — 访问敏感数据,必须在运行时请求(CAMERA、LOCATION、READ_CONTACTS)
  • 签名权限 — 仅授予使用同一证书签名的应用

在 Manifest 中声明

所有权限都必须先在 AndroidManifest.xml 中声明:

<!-- AndroidManifest.xml -->
<manifest ...>

    <!-- Normal: granted automatically -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.VIBRATE" />

    <!-- Dangerous: must request at runtime -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />

    <application ...>
        ...
    </application>
</manifest>

检查是否已授予权限

使用危险功能前,请始终检查是否已授予相应权限:

import androidx.core.content.ContextCompat
import android.content.pm.PackageManager

fun isCameraPermissionGranted(): Boolean {
    return ContextCompat.checkSelfPermission(
        this,   // Context
        android.Manifest.permission.CAMERA
    ) == PackageManager.PERMISSION_GRANTED
}

使用 ActivityResultLauncher 请求权限

使用 ActivityResultContracts.RequestPermission()——这是现代方式(无需使用已弃用的 onRequestPermissionsResult):

class CameraActivity : AppCompatActivity() {

    private val requestCameraPermission = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (isGranted) {
            openCamera()
        } else {
            showPermissionDeniedMessage()
        }
    }

    fun onTakePhotoClick(view: View) {
        if (isCameraPermissionGranted()) {
            openCamera()
        } else {
            requestCameraPermission.launch(android.Manifest.permission.CAMERA)
        }
    }
}

同时请求多个权限

使用 RequestMultiplePermissions 请求多个权限:

private val requestMultiple = registerForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
    val cameraGranted   = permissions[android.Manifest.permission.CAMERA] ?: false
    val locationGranted = permissions[android.Manifest.permission.ACCESS_FINE_LOCATION] ?: false

    if (cameraGranted && locationGranted) {
        startFeature()
    } else {
        showWhichPermissionsDenied(cameraGranted, locationGranted)
    }
}

fun requestPermissions() {
    requestMultiple.launch(arrayOf(
        android.Manifest.permission.CAMERA,
        android.Manifest.permission.ACCESS_FINE_LOCATION
    ))
}

说明理由:何时解释原因

当用户之前拒绝过权限时,shouldShowRequestPermissionRationale() 会返回 true。再次请求前,请先显示解释:

fun requestCameraWithRationale() {
    when {
        isCameraPermissionGranted() -> {
            openCamera()
        }
        shouldShowRequestPermissionRationale(android.Manifest.permission.CAMERA) -> {
            // Explain why we need it, then request
            AlertDialog.Builder(this)
                .setTitle("Camera needed")
                .setMessage("We use the camera to take profile photos.")
                .setPositiveButton("OK") { _, _ ->
                    requestCameraPermission.launch(android.Manifest.permission.CAMERA)
                }
                .show()
        }
        else -> {
            requestCameraPermission.launch(android.Manifest.permission.CAMERA)
        }
    }
}

永久拒绝——引导至设置

如果用户点击了“不要再询问”,shouldShowRequestPermissionRationale 会返回 false,您也无法再次请求权限。请引导用户前往应用设置:

fun openAppSettings() {
    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
        data = Uri.fromParts("package", packageName, null)
    }
    startActivity(intent)
}

位置权限

位置权限分为两个级别,请根据使用场景选择正确的级别:

  • ACCESS_COARSE_LOCATION——城市级精度(WiFi/基站)
  • ACCESS_FINE_LOCATION——GPS 精度
  • ACCESS_BACKGROUND_LOCATION——在 Android 10 及更高版本中,若要在后台访问位置,则必须单独请求

相机权限完整示例

结合检查 → 说明理由 → 请求的完整流程:

class PhotoActivity : AppCompatActivity() {

    private val cameraLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { granted ->
        if (granted) openCamera() else showDenied()
    }

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

    private fun showDenied() {
        Snackbar.make(binding.root, "Camera permission required", Snackbar.LENGTH_LONG)
            .setAction("Settings") { openAppSettings() }.show()
    }
}

权限最佳实践

打造顺畅权限体验的关键规则:

  • 仅在需要时请求权限(不要在应用启动时请求)
  • 只请求所需的最低权限
  • 始终妥善处理用户拒绝的情况
  • 用户选择“不要再询问”后,不要反复请求
  • 在系统对话框出现之前解释原因,而不是被拒绝后再解释

Android 13 及更高版本的细粒度媒体权限

从 Android 13(API 33)开始,READ_EXTERNAL_STORAGE 被拆分为更细粒度的权限:

  • READ_MEDIA_IMAGES
  • READ_MEDIA_VIDEO
  • READ_MEDIA_AUDIO

请始终检查 Build.VERSION.SDK_INT,并根据 OS 版本请求正确的权限。

快速检查

在请求危险权限前,何时应显示说明理由的对话框?

回顾:权限

正确处理运行时权限:

  • 在 AndroidManifest.xml 中声明
  • 使用 ContextCompat.checkSelfPermission() 检查
  • 通过 registerForActivityResult(RequestPermission()) 请求
  • 当 shouldShowRequestPermissionRationale() 为 true 时显示说明理由
  • 对于永久拒绝,引导用户前往 Settings
  • 切勿在用户需要权限之前请求

下一步:使用 DataStore 在本地保存数据。

免费开始

用 AI 导师学习 Kotlin — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
36
课程
152

常见问题解答

「权限与运行时请求」课时是免费的吗?

是的 — 「权限与运行时请求」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Android Academy 课程的其余内容,请升级到 CoddyKit PRO。 Android Academy 课程共包含 7 节课。

「权限与运行时请求」这节课中我会学到什么?

使用 ActivityResultContracts 在运行时声明并请求危险权限,显示权限说明对话框,并处理永久拒绝。 你通过在浏览器中直接运行的动手代码来练习 Android Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Android Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Android Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 7 节课,共 7 节。

「权限与运行时请求」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Android Academy 课中编写并运行代码吗?

能。每节 Android Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 项目结构与清单文件
  2. Activity 与生命周期
  3. 布局与视图
  4. 处理用户输入
  5. Intent 与导航
  6. Fragment
  7. 权限与运行时请求
← 返回 Android Academy