Flow监听权限状态变化实践 - 第4节
4·1 为什么需要 ActivityResultLauncher
Android 11(API 30)开始,startActivityForResult() 被标记为废弃。官方推荐使用 ActivityResultLauncher 配合 ActivityResultContracts 来获取权限请求结果。这一变化的核心动机:
| 对比项 | 旧方式 (onRequestPermissionsResult) | 新方式 (ActivityResultLauncher) |
|---|---|---|
| 回调位置 | 分散在 Activity/Fragment 多个生命周期方法 | 注册时一次性定义,回调集中 |
| 生命周期耦合 | 可能因重建导致回调丢失 | 自动跟随 Lifecycle,重建后重新注册 |
| 代码可读性 | 需要 switch-case 判断 requestCode | 使用契约泛型,类型安全,无 requestCode |
| 集成其他组件 | 需手动处理恢复状态 | 配合 SavedStateHandle 可自动恢复 |
课程定位:本章只聚焦
ActivityResultLauncher 的注册与回调处理。后续章节将在此基础上结合 Kotlin Flow 进行响应式封装。
4·2 注册启动器的两种方式
通常推荐在 Activity 或 Fragment 的 onCreate / onViewCreated 中注册启动器,确保在首次生命周期回调完成前完成注册。
4·2·1 使用 registerForActivityResult()
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
// 回调:用户操作后触发
if (isGranted) {
// 权限已授予
} else {
// 权限被拒绝(可能包含“不再询问”状态)
}
}
- 第一个参数:
ActivityResultContract实现类,权限请求使用RequestPermission或RequestMultiplePermissions。 - 第二个参数:回调
ActivityResultCallback,在用户做出权限决策后执行。 - 返回值:
ActivityResultLauncher<String>(单权限)或ActivityResultLauncher<Array<String>>(多权限)。
4·2·2 使用 prepareCall()(已废弃,仅作了解)
早期版本提供 prepareCall(),在 AndroidX Activity 1.2.0 后被 registerForActivityResult() 取代。务必使用新 API。
4·3 发起权限请求
注册完成后,在需要权限时调用 launch() 方法:
// 单权限请求
buttonRequestCamera.setOnClickListener {
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
// 多权限请求(使用 RequestMultiplePermissions 注册)
private val multiplePermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions: Map<String, Boolean> ->
permissions.forEach { (perm, granted) ->
// 分别处理每个权限结果
}
}
buttonRequestMultiple.setOnClickListener {
multiplePermissionLauncher.launch(
arrayOf(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION)
)
}
重要:
launch() 必须在注册的 Activity 或 Fragment 处于生命周期 STARTED 及以上状态时调用,否则会抛出 IllegalStateException。建议在用户交互(如按钮点击)时触发,不要在 onCreate 中直接调用。
4·4 处理回调的完整模式
回调中不仅要处理授权/拒绝,还要应对“不再询问”场景。推荐通过 shouldShowRequestPermissionRationale() 判断:
private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
// 权限已授权,执行对应操作
} else {
// 检查是否应显示 Rationale(用户勾选了“不再询问”时返回 false)
if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
// 用户勾选“不再询问”,引导前往设置页面
showPermissionSettingsDialog()
} else {
// 用户拒绝但未勾选“不再询问”
showPermissionRationale()
}
}
}
4·5 生命周期与重建安全
ActivityResultLauncher 内部使用 Lifecycle 管理注册和销毁。当 Activity 重建时:
- 系统会通过
SavedStateHandle保留启动器的中间状态。 - 如果重建前已调用
launch()但用户未返回结果,重建后回调不会丢失。 - 开发人员只需确保在
onCreate中重新注册,即可自动恢复。
4·6 完整流程与 SVG 框架图
下图展示了从注册到回调的完整生命周期:
4·7 调试与部署要点
调试技巧:
- 在回调中添加日志:
Log.d("PermLauncher", "result: $isGranted")。 - 使用
adb shell dumpsys activity permissions查看当前应用权限状态。 - 模拟“不再询问”场景:前往设置 → 应用信息 → 权限 → 选择“拒绝并不再询问”。
稳定性保证:即使 Activity 因配置变更重建,只要注册代码放在
onCreate 中,ActivityResultLauncher 会自动恢复待处理的请求。不要使用 lateinit var 存储 launcher,改为 val 直接在类属性中注册更安全。
4·8 常见错误与解决方案
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
IllegalStateException: LifecycleOwner is attempting to register while current state is RESUMED | 在 Activity 已 resumed 后注册 | 将注册代码移到 onCreate 头部,使用 lifecycleScope.launch 延迟注册时注意状态 |
| 回调未执行或重复执行 | 注册放在 onResume 导致多次注册 | 始终在 onCreate 中注册,且只注册一次 |
launch() 调用后无反应 | 权限已被授予或 manifest 未声明权限 | 先检查 ContextCompat.checkSelfPermission,未授权再 launch;同时确认 AndroidManifest.xml 包含对应权限声明 |
| 多权限结果 Map 为空 | 使用了 RequestMultiplePermissions 但传入空数组 | 确保数组至少包含一个权限 |
4·9 本章总结
- 核心概念:
ActivityResultLauncher取代旧的startActivityForResult/onRequestPermissionsResult。 - 注册位置:必须在
onCreate或onViewCreated中注册,保证生命周期安全。 - 回调处理:结合
shouldShowRequestPermissionRationale处理“不再询问”场景。 - 性能与稳定性:自动跟随 Lifecycle,重建不丢失结果。