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 上。
实现流程
- 在
Activity或Fragment初始化阶段注册 Launcher; - 调用
launch(permission)触发系统对话框; - 系统在用户操作后自动调用注册时传入的
ActivityResultCallback; - 在回调中判断
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 官方最早的运行时权限回调方案。
实现流程
- 定义请求码常量(如
REQUEST_CAMERA); - 调用
requestPermissions(permissions, requestCode); - 系统弹出对话框,用户操作后触发
onRequestPermissionsResult; - 在回调中按
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,而是一种设计模式:将权限请求的上下文(权限类型、回调)封装为一个 对象,在请求发起时暂存,在结果返回时取出并执行回调。常用于解耦请求发起者与结果处理者,但仍无法脱离系统回调的基础设施。
实现流程
- 定义
PermissionToken类,持有回调引用; - 在请求发起处创建 并传入回调;
- 被暂存在中间层(如
PermissionManager); - 系统回调到达后,从中间层取出对应的 并触发回调。
代码示例
// 封装
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 三种传统流程对比总览
下图从请求发起到结果处理的宏观视角,对比三种方式的核心流转路径。注意三者均以线性回调为终点,无法自然融入协程的挂起-恢复模型。