题目1:问题定义与场景分析

Android 运行时权限模型自 6.0(API 23)引入后,应用需在运行时动态申请敏感权限。传统的权限请求基于 ActivityCompat.requestPermissions() + onRequestPermissionsResult() 回调,或使用 ActivityResultContracts.RequestMultiplePermissions 的 Launcher 方式。这些方案在复杂逻辑(多重权限链、条件驳回、权限降级回退)下极易导致代码碎片化、生命周期耦合以及测试困难。

本课程专注于 协程异步权限请求流程设计,利用 Kotlin 协程的结构化并发能力,将权限请求从回调驱动的乱流转换为声明式挂起函数,从而解决传统实现中的核心痛点。

常见痛点

痛点 描述 传统写法示例
回调地狱 嵌套多层回调,逻辑散落在不同生命周期回调中,可读性差
requestPermissions(perms, REQ);
// 回调中再判断 grant 结果
// 再调用另一个权限请求……
生命周期泄漏 权限结果回调时 Activity 可能已销毁,导致 IllegalStateException 或内存泄漏
@Override
public void onRequestPermissionsResult(...) {
    // 未检查 isFinishing()
}
多权限组合复杂 一次性请求多个权限时,必须等待所有结果返回后再合成状态,逻辑判断臃肿
if (grantResults.length > 0 && grantResults[0] == GRANTED && ...) {}
用户拒绝后难以优雅降级 用户点击“拒绝”后,应用需要判断是否应该弹窗解释;若再次拒绝则永久拒绝,状态机复杂
if (shouldShowRequestPermissionRationale(...)) {
    // 弹解释对话框
} else {
    // 跳系统设置
}
单元测试困难 回调依赖 Activity/Fragment 实例,难以使用纯单元测试 mock 权限结果
// 需要 Robolectric 或 Instrumentation 测试
关键认识:以上痛点的本质是“异步控制流与 Android 生命周期的交错耦合”。协程通过 suspend 函数、CoroutineScope 和 Dispatchers.Main 提供了一种线性挂起的能力,天然适合将权限请求封装为可恢复的点,从而消除回调嵌套。

设计目标

基于痛点,协程异步权限请求流程应达成以下目标:

  • 结构化并发 —— 每个权限请求绑定到指定 CoroutineScope(如 viewModelScope 或 lifecycleScope),Activity 销毁时自动取消,避免泄漏。
  • 声明式 API —— 用一个挂起函数表达“请求权限并等待结果”,调用方不再接触回调。
  • 生命周期安全 —— 内部自动处理 isFinishing、onSaveInstanceState 等边界,保证结果安全分发。
  • 支持多权限组合 —— 提供 all(全部授权) / any(任一授权)等组合语义,减少条件判断。
  • 可测试性 —— 权限请求逻辑可被注入 mock 权限检查器,支持单元测试和集成测试。
  • 用户友好 —— 内置“是否应弹解释对话框”判断和“前往设置”跳转封装,减少样板代码。

以下 SVG 图展示了传统回调式权限请求与协程异步权限请求的流程对比,以及设计目标如何解决痛点。

传统回调 vs 协程异步权限请求问题域 传统回调痛点 ① 回调地狱 ② 生命周期泄漏 ③ 多权限组合复杂 ④ 拒绝降级难管理 ⑤ 单元测试困难 协程 异步 封装 协程设计目标 结构化并发 · 自动取消 声明式挂起函数 API 生命周期安全分发 多权限组合语义 可测试性 · mock 注入 核心设计思路 将每个权限请求生命周期绑定到 CoroutineScope,用 suspend fun 替代回调, 配合 Flow / Channel 实现权限状态的声明式组合,从而根除以上痛点。
注意:协程并非自动消除所有权限复杂度。若不当处理 withContext(Dispatchers.Main) 或跨协程取消,仍可能出现“权限已挂起但界面已关闭”的异常。后续章节将逐一讲解如何用 suspendCancellableCoroutine 实现安全的一步请求。