权限声明与运行时请求:AndroidManifest + rememberLauncherForActivityResult
相机功能是敏感操作,Android 自 6.0(API 23)起强制执行运行时权限模型。即使已在 AndroidManifest.xml 中声明,应用仍须在用户首次使用相机时弹出系统对话框请求授权,用户可选择「允许」或「拒绝」。拒绝后再次请求时,系统会提供「不再询问」选项,开发者必须妥善处理这些状态。
1. 原理与层级
- 清单声明:告知系统应用需要相机硬件。若缺失,即使运行时请求成功也无法使用相机。
- 运行时请求:通过 ActivityResult API 启动系统权限对话框,在回调中获取结果。
- 状态处理:需要区分「已授权」「已拒绝」「拒绝且不再询问」三种状态,并给出相应 UI 提示或引导。
核心 API:
rememberLauncherForActivityResult + ActivityResultContracts.RequestPermission
2. 实现流程
- 在
AndroidManifest.xml中添加相机权限声明。 - 在 Composable 函数中通过
rememberLauncherForActivityResult创建权限请求启动器。 - 检查当前权限是否已授予(
ContextCompat.checkSelfPermission)。 - 若未授权,调用启动器的
launch()方法。 - 在回调中根据
isGranted布尔值更新 UI 状态。 - 处理二次拒绝、权限永久拒绝(需跳转系统设置)等情况。
最佳实践:始终先判断是否应显示 rationale(解释为何需要权限),再触发请求。Android 提供了
shouldShowRequestPermissionRationale 方法。
3. 清单声明
在 app/src/main/AndroidManifest.xml 的 <manifest> 内添加:
<uses-permission android:name="android.permission.CAMERA" />
<!-- 如果应用仅在支持相机的设备上运行,可以添加: -->
<uses-feature android:name="android.hardware.camera" android:required="true" />
注意:
android:required="true" 会使不支持相机的设备无法安装应用。若允许使用用户头像等可选功能,应设为 false 并在代码中检查 PackageManager.hasSystemFeature。
4. 运行时请求代码
@Composable
fun CameraPermissionGate(
onGranted: () -> Unit,
onDenied: (shouldShowRationale: Boolean) -> Unit
) {
val context = LocalContext.current
var permissionState by remember { mutableStateOf(PermissionState.UNKNOWN) }
val permissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission()
) { isGranted ->
permissionState = if (isGranted) PermissionState.GRANTED else PermissionState.DENIED
}
LaunchedEffect(Unit) {
when {
ContextCompat.checkSelfPermission(
context, Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
permissionState = PermissionState.GRANTED
onGranted()
}
else -> {
val shouldShowRationale = ActivityCompat.shouldShowRequestPermissionRationale(
context as Activity, Manifest.permission.CAMERA
)
permissionState = PermissionState.REQUESTING
permissionLauncher.launch(Manifest.permission.CAMERA)
// 实际授权结果在回调中处理
}
}
}
when (permissionState) {
PermissionState.GRANTED -> { /* 显示相机预览 */ }
PermissionState.DENIED -> {
val shouldShowRationale = ActivityCompat.shouldShowRequestPermissionRationale(
context as Activity, Manifest.permission.CAMERA
)
onDenied(shouldShowRationale)
}
PermissionState.REQUESTING -> { /* 显示加载或等待状态 */ }
PermissionState.UNKNOWN -> { /* 初始状态 */ }
}
}
enum class PermissionState {
UNKNOWN, GRANTED, DENIED, REQUESTING
}
5. 调试与验证
- 模拟拒绝:在模拟器中第一次弹出对话框时选择「拒绝」。
- 模拟「不再询问」:在真机上连续拒绝两次,第三次请求时系统会自动拒绝且不弹出对话框。
- adb 命令快速测试:
# 授予相机权限 adb shell pm grant com.example.cameraapp android.permission.CAMERA # 撤销相机权限 adb shell pm revoke com.example.cameraapp android.permission.CAMERA - 权限日志:在回调中添加日志
Log.d("Permission", "Granted=$isGranted")并查看 logcat。
6. 常见错误
| 错误现象 | 原因 | 解决 |
|---|---|---|
调用 CameraX.bindToLifecycle 时抛出 SecurityException |
权限未授予即尝试访问相机 | 确保进入相机预览前已检查权限状态 |
| 权限对话框不弹出 | 清单中未声明 CAMERA 权限,或混淆规则移除了权限 |
检查 AndroidManifest.xml 及 ProGuard 规则 |
| 第二次请求时对话框不再弹出 | 用户勾选了「不再询问」 | 应跳转系统设置页面引导用户手动开启 |
shouldShowRequestPermissionRationale 返回永远 false |
权限被永久拒绝后或设备策略限制 | 直接视为无法再请求,提供跳转设置按钮 |
7. 性能与稳定性优化
- 避免重复请求:使用状态机管理权限阶段,只在
PermissionState.UNKNOWN或DENIED时触发请求。 - Compose 重组优化:将权限启动器定义在顶层 Composable,避免重组时重新创建。
- 生命周期安全:
rememberLauncherForActivityResult内部已处理 Activity 重建,无需额外保存实例状态。 - 权限降级防护:即使以前授予过,也应在每次打开相机前做运行时检查,因为用户可能在系统设置中撤销权限。
- 多权限场景:若同时需要相机和录音权限,改用
RequestMultiplePermissions合同,避免多次弹出对话框。
提示:Android 11(API 30)引入了「自动重置权限」功能,长时间未使用的应用会被系统自动撤销权限。应在
onResume 或 LaunchedEffect 中重新检查权限状态。