4. 相机权限处理:Android 6.0+ 运行时权限请求

在 Android 6.0(API 23)及更高版本中,系统引入了运行时权限模型。应用不再在安装时一次性授予所有权限,而是需要在应用运行时动态请求用户授权。对于自定义相机应用,必须处理 CAMERA 权限(用于打开相机预览和拍照)以及 WRITE_EXTERNAL_STORAGE 权限(用于将照片保存到设备存储)。本章将系统讲解运行时权限的请求流程、最佳实践及常见问题处理。

核心原则: 权限请求必须与用户操作场景强关联。不要在应用启动时一次性请求所有权限,而应在用户即将使用对应功能时发起请求,并提供清晰的说明。

4.1 运行时权限模型概述

运行时权限模型将权限分为正常权限和危险权限两类。正常权限由系统自动授予,危险权限则需要运行时动态请求。CAMERA 和 WRITE_EXTERNAL_STORAGE 均属于危险权限。

权限名称 权限常量 所属权限组 危险级别
相机 android.permission.CAMERA CAMERA 危险
写入外部存储 android.permission.WRITE_EXTERNAL_STORAGE STORAGE 危险

运行时权限请求的核心流程包含以下步骤:

  1. 检查权限状态: 使用 ContextCompat.checkSelfPermission() 判断权限是否已被授予。
  2. 解释请求原因: 如果用户之前拒绝过权限,应使用 shouldShowRequestPermissionRationale() 判断是否需要显示解释说明。
  3. 发起权限请求: 调用 ActivityCompat.requestPermissions() 弹出系统权限对话框。
  4. 处理请求结果: 在 onRequestPermissionsResult() 回调中处理用户的授权或拒绝操作。
关键 API:
  • ContextCompat.checkSelfPermission(context, permission) — 检查权限状态
  • ActivityCompat.shouldShowRequestPermissionRationale(activity, permission) — 判断是否需要显示解释
  • ActivityCompat.requestPermissions(activity, permissions, requestCode) — 发起请求
  • onRequestPermissionsResult(requestCode, permissions, grantResults) — 处理结果

4.2 权限请求流程图

下图展示了完整的权限请求决策流程,帮助理解每个分支的处理逻辑:

Android 6.0+ 运行时权限请求流程 开始权限请求 检查权限状态 已授权? checkSelfPermission 是 直接使用功能 否 需要解释? shouldShowRationale 是 显示解释说明 否 发起权限请求 处理请求结果 用户授权? 是 功能可用 否 提示用户手动开启 解释后发起请求

4.3 在 AndroidManifest.xml 中声明权限

无论运行时如何请求,首先必须在 AndroidManifest.xml 中声明所需权限。这是权限工作的前提条件。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.customcamera">

    <!-- 相机权限 -->
    <uses-permission android:name="android.permission.CAMERA" />

    <!-- 写入外部存储权限(用于保存照片) -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />

    <!-- 读取外部存储权限(Android 10+ 推荐使用) -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="32" />

    <!-- 相机硬件特性声明(非必需,但建议添加) -->
    <uses-feature android:name="android.hardware.camera" android:required="true" />

    ...
</manifest>
注意: 从 Android 10(API 29)开始,WRITE_EXTERNAL_STORAGE 权限不再提供真正的写入访问权限,系统推荐使用 Scoped Storage(分区存储)。对于相机应用,建议使用 MediaStore API 保存照片,仅在 Android 9 及以下设备上请求 WRITE_EXTERNAL_STORAGE。上述代码中 android:maxSdkVersion="28" 即为此目的。

4.4 运行时权限请求完整实现

以下代码展示了在 Activity 中请求 CAMERA 和 WRITE_EXTERNAL_STORAGE 权限的完整实现:

public class CameraActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_PERMISSIONS = 100;
    private static final String[] REQUIRED_PERMISSIONS = {
        Manifest.permission.CAMERA,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);

        // 检查并请求权限
        if (allPermissionsGranted()) {
            openCamera();
        } else {
            requestPermissionsIfNeeded();
        }
    }

    private boolean allPermissionsGranted() {
        for (String permission : REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission)
                    != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    private void requestPermissionsIfNeeded() {
        List<String> permissionsToRequest = new ArrayList<>();

        for (String permission : REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission)
                    != PackageManager.PERMISSION_GRANTED) {
                permissionsToRequest.add(permission);
            }
        }

        if (!permissionsToRequest.isEmpty()) {
            ActivityCompat.requestPermissions(this,
                    permissionsToRequest.toArray(new String[0]),
                    REQUEST_CODE_PERMISSIONS);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                openCamera();
            } else {
                // 权限被拒绝,检查是否需要显示解释
                boolean shouldShowRationale = false;
                for (String permission : REQUIRED_PERMISSIONS) {
                    if (ActivityCompat.shouldShowRequestPermissionRationale(
                            this, permission)) {
                        shouldShowRationale = true;
                        break;
                    }
                }

                if (shouldShowRationale) {
                    // 用户拒绝但未勾选"不再询问",可再次解释并请求
                    showPermissionExplanationDialog();
                } else {
                    // 用户勾选了"不再询问",引导用户前往设置
                    showSettingsRedirectDialog();
                }
            }
        }
    }

    private void openCamera() {
        // 初始化相机预览和拍照功能
        // 后续章节实现
    }

    private void showPermissionExplanationDialog() {
        new AlertDialog.Builder(this)
                .setTitle("需要相机权限")
                .setMessage("自定义相机应用需要相机权限来拍照," +
                        "以及存储权限来保存照片。请授予相关权限。")
                .setPositiveButton("重新请求", (dialog, which) -> {
                    requestPermissionsIfNeeded();
                })
                .setNegativeButton("取消", null)
                .show();
    }

    private void showSettingsRedirectDialog() {
        new AlertDialog.Builder(this)
                .setTitle("权限被永久拒绝")
                .setMessage("相机或存储权限已被永久拒绝," +
                        "请在系统设置中手动开启。")
                .setPositiveButton("前往设置", (dialog, which) -> {
                    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                    Uri uri = Uri.fromParts("package", getPackageName(), null);
                    intent.setData(uri);
                    startActivity(intent);
                })
                .setNegativeButton("取消", null)
                .show();
    }
}

4.5 权限请求最佳实践

在实际开发中,遵循以下最佳实践可以显著提升用户体验和权限授予率:

实践要点 说明 推荐做法
时机选择 在用户即将使用相机功能时请求,而非应用启动时 点击"拍照"按钮后触发权限请求
解释说明 在请求前向用户说明为什么需要该权限 使用 Snackbar 或 Dialog 提供简短说明
拒绝处理 区分"拒绝一次"和"永久拒绝"两种场景 使用 shouldShowRequestPermissionRationale() 判断
设置引导 权限被永久拒绝时,引导用户前往系统设置 使用 Settings.ACTION_APPLICATION_DETAILS_SETTINGS
批量请求 将相关权限合并为一次请求,减少对话框次数 使用字符串数组一次性请求多个权限
兼容性 确保代码在 Android 6.0 以下设备正常运行 使用 ContextCompat 和 ActivityCompat 兼容类

4.6 权限状态与用户决策路径

用户与权限对话框的交互会产生三种不同的状态,每种状态对应不同的处理策略:

权限请求用户决策路径 权限请求对话框 授权 功能正常使用 拒绝(不勾选"不再询问") 下次可再次请求 拒绝(勾选"不再询问") 引导前往系统设置 关键判断方法:shouldShowRequestPermissionRationale() 返回 true → 用户拒绝过但未勾选"不再询问"(分支2) 返回 false → 首次请求 或 用户已勾选"不再询问"(分支3)

4.7 常见问题与解决方案

问题 1:权限请求对话框不弹出

原因: 未在 AndroidManifest.xml 中声明权限,或使用了错误的权限字符串。

解决: 检查 manifest 中是否包含 <uses-permission android:name="android.permission.CAMERA" />,并确认权限常量拼写正确。

问题 2:用户拒绝权限后应用崩溃

原因: 未处理权限被拒绝的情况,直接调用了需要权限的 API。

解决: 在 onRequestPermissionsResult 中检查 grantResults,权限未授予时给出友好提示,避免执行敏感操作。

问题 3:Android 10+ 设备上照片保存失败

原因: Android 10 引入了分区存储,WRITE_EXTERNAL_STORAGE 权限不再提供直接文件路径写入能力。

解决: 使用 MediaStore API 保存照片,或使用 FileProvider 生成 content URI。对于 Android 10+,不再需要 WRITE_EXTERNAL_STORAGE 权限。

4.8 本章小结

权限处理是自定义相机应用的基础保障。本章涵盖了以下核心知识点:

  • 运行时权限模型: Android 6.0+ 要求危险权限在运行时动态请求。
  • 权限声明: 在 AndroidManifest.xml 中声明 CAMERA 和 WRITE_EXTERNAL_STORAGE 权限。
  • 请求流程: 检查状态 → 解释原因 → 发起请求 → 处理结果。
  • 用户决策处理: 区分授权、拒绝一次、永久拒绝三种场景。
  • 最佳实践: 时机选择、解释说明、设置引导、兼容性处理。