4. 传统权限请求方式及其局限

在 Android 运行时权限机制中,传统请求方式经历了从原生回调到封装模式的演变。本章深入剖析 ActivityResultLauncher、onRequestPermissionsResult 回调以及 模式 的实现原理、核心流程与代码结构,并系统性揭示它们在高并发、生命周期敏感场景下的共同局限,为后续引入协程异步方案建立对比基准。

4.1 三种传统方式的技术背景

Android 6.0 (API 23) 引入运行时权限后,官方先后提供了两种原生 API:早期基于 onRequestPermissionsResult 的回调方式,以及 AndroidX Activity 1.2.0+ 推出的 Activity Result API(即 ActivityResultLauncher)。 模式则是社区为解耦回调与 Activity 生命周期而设计的轻量封装。三者本质上都依赖 Activity 上下文 和 线性回调 来传递授权结果。

核心认知:三种方式均未解决“回调与协程作用域分离”的问题,每次请求都需要手动维护状态,在多个权限串联请求时尤其脆弱。

4.2 ActivityResultLauncher 方式

原理

基于 ComponentActivity.registerForActivityResult() 注册一个 ActivityResultLauncher,内部通过 ActivityResultContract 启动系统权限对话框,并在注册时传入的回调中处理授权结果。其本质仍是一次性回调,只不过将回调绑定到了 LifecycleOwner 上。

实现流程

  1. 在 Activity 或 Fragment 初始化阶段注册 Launcher;
  2. 调用 launch(permission) 触发系统对话框;
  3. 系统在用户操作后自动调用注册时传入的 ActivityResultCallback;
  4. 在回调中判断 isGranted 进行分支处理。

代码示例

class CameraActivity : AppCompatActivity() {

    // 步骤1:注册 Launcher,回调在注册时绑定
    private val requestCameraLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
            if (isGranted) {
                openCamera()
            } else {
                showPermissionDeniedUI()
            }
        }

    fun onClickCamera() {
        // 步骤2:启动请求
        requestCameraLauncher.launch(Manifest.permission.CAMERA)
    }
}

主要局限

  • 遗漏回调风险:若在 onDestroy 后回调才触发,可能操作已销毁的视图;
  • 多请求管理困难:每个权限需独立注册 Launcher,代码膨胀;
  • 无法直接返回结果给 ViewModel:必须通过 LiveData / Flow 中转,增加样板代码。

4.3 onRequestPermissionsResult 回调方式

原理

通过重写 Activity.onRequestPermissionsResult() 方法,接收系统返回的 requestCode、permissions 数组和 grantResults 数组,根据 requestCode 进行分发处理。这是 Android 官方最早的运行时权限回调方案。

实现流程

  1. 定义请求码常量(如 REQUEST_CAMERA);
  2. 调用 requestPermissions(permissions, requestCode);
  3. 系统弹出对话框,用户操作后触发 onRequestPermissionsResult;
  4. 在回调中按 requestCode 匹配并解析 grantResults。

代码示例

class CameraActivity : AppCompatActivity() {

    companion object {
        private const val REQUEST_CAMERA = 1001
    }

    fun onClickCamera() {
        requestPermissions(
            arrayOf(Manifest.permission.CAMERA),
            REQUEST_CAMERA
        )
    }

    // 重写系统回调方法
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        when (requestCode) {
            REQUEST_CAMERA -> {
                if (grantResults.isNotEmpty() &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED
                ) {
                    openCamera()
                } else {
                    showPermissionDeniedUI()
                }
            }
        }
    }
}

主要局限

  • 模板代码爆炸:每个请求需定义请求码、匹配分支、解析数组;
  • 生命周期脆弱:配置变更(如旋转屏幕)会导致回调丢失或重复触发;
  • 类型不安全:requestCode 为整数常量,无编译期校验;
  • 不易测试:回调与 Activity 强耦合,单元测试困难。

4.4  模式

原理

模式并非官方 API,而是一种设计模式:将权限请求的上下文(权限类型、回调)封装为一个 对象,在请求发起时暂存,在结果返回时取出并执行回调。常用于解耦请求发起者与结果处理者,但仍无法脱离系统回调的基础设施。

实现流程

  1. 定义 PermissionToken 类,持有回调引用;
  2. 在请求发起处创建 并传入回调;
  3. 被暂存在中间层(如 PermissionManager);
  4. 系统回调到达后,从中间层取出对应的 并触发回调。

代码示例

//  封装
class PermissionToken(
    val permission: String,
    private val onResult: (Boolean) -> Unit
) {
    fun resolve(granted: Boolean) {
        onResult(granted)
    }
}

// 中间层管理对象
object PermissionManager {
    private val tokenQueue = mutableMapOf<String, PermissionToken>()

    fun enqueue(: PermissionToken) {
        tokenQueue[.permission] = 
    }

    fun dequeue(permission: String): PermissionToken? {
        return tokenQueue.remove(permission)
    }
}

// 在 Activity 中使用
class CameraActivity : AppCompatActivity() {

    fun onClickCamera() {
        val  = PermissionToken(Manifest.permission.CAMERA) { granted ->
            if (granted) openCamera() else showDenied()
        }
        PermissionManager.enqueue()
        requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA)
    }

    override fun onRequestPermissionsResult(...) {
        // 从  队列中取出并执行
        val  = PermissionManager.dequeue(Manifest.permission.CAMERA)
        ?.resolve(grantResults[0] == PackageManager.PERMISSION_GRANTED)
    }
}

主要局限

  • 回调链复杂:多个权限串联请求时, 嵌套管理困难;
  • 内存泄漏风险: 若未及时出队,会持有 Activity 引用导致泄漏;
  • 无协程取消机制: 无法响应协程取消,容易产生悬空回调。

4.5 三种方式局限对比

维度 ActivityResultLauncher onRequestPermissionsResult 模式
生命周期耦合 中(自动解注册但回调可能滞后) 高(配置变更回调丢失) 高( 持有隐式引用)
代码简洁性 中(每个权限一个 Launcher) 低(请求码 + 回调分支) 中(需额外管理队列)
多请求串联 困难(回调嵌套) 困难(需状态机) 困难( 链式管理)
可测试性 低(依赖 Activity) 低(依赖 Activity) 中(可隔离 逻辑)
协程原生支持 无 无 无
异常安全 中(未处理异常会崩溃) 低(回调中易遗漏异常) 低( 超时无保护)
关键结论:三种方式均缺少对协程作用域和结构化并发的支持,无法在请求失败或用户取消时自动清理挂起操作,这正是后续引入协程异步方案的根本动因。

4.6 常见错误与调试

  • Launcher 未注册就 launch:抛出 IllegalStateException。应在 onCreate 或 onAttach 中完成注册,并且一个 launcher 只能 launch 一次,重复调用会覆盖上一次的回调;
  • onRequestPermissionsResult 回调未调用:常见于 Activity 未重写该方法,或 super 调用顺序错误。检查清单中是否声明了对应权限;
  • 模式回调泄漏:若 Activity 销毁前未清理 PermissionManager 中的 ,将导致内存泄漏。可在 onDestroy 中清空队列;
  • 配置变更后结果丢失:onRequestPermissionsResult 在 Activity 重建后不会重新分发。建议结合 SavedStateHandle 或 ViewModel 持久化结果;
  • 权限被拒绝且不再询问:三种方式均无法区分“拒绝一次”和“永久拒绝”,需要额外调用 shouldShowRequestPermissionRationale() 判断。
调试建议:在 onRequestPermissionsResult 或 Launcher 回调入口处添加详细日志,记录 requestCode、permissions 和 grantResults,便于定位回调未触发或结果错误的问题。

4.7 性能与稳定性优化

  • 避免重复请求:在发起请求前检查 checkSelfPermission,权限已授予则直接执行逻辑,不触发系统对话框;
  • 超时保护: 模式可加入超时机制,超过 30 秒未收到结果自动执行失败回调并移除 ;
  • 生命周期安全回调:使用 Lifecycle.repeatOnLifecycle 或 LiveData 包装回调,确保在不可见状态不会更新 UI;
  • 合并请求:将同一组权限合并为一次 requestPermissions 调用,减少对话框弹出次数;
  • 统一分发层:在 BaseActivity 中封装统一的权限回调分发逻辑,避免子类重复编写模板代码。

4.8 三种传统流程对比总览

下图从请求发起到结果处理的宏观视角,对比三种方式的核心流转路径。注意三者均以线性回调为终点,无法自然融入协程的挂起-恢复模型。

传统权限请求三种方式流程对比 传统权限请求三种方式流程对比 ActivityResultLauncher ① registerForActivityResult (绑定 LifecycleOwner) ② launcher.launch() (触发系统对话框) ③ ActivityResultCallback (isGranted 分支) onRequestPermissionsResult ① 定义 requestCode 常量 (如 REQUEST_CAMERA = 1001) ② requestPermissions() (传递 array + requestCode) ③ 重写回调方法 (按 requestCode 匹配分支) 模式 ① 创建 PermissionToken (封装权限 + 回调) ② enqueue + requestPermissions (暂存 到队列) ③ dequeue → .resolve() (执行回调并清理队列) ⚠ 三大方式共性局限 生命周期耦合 · 模板代码多 · 多请求串联困难 · 无协程原生支持