Permission Handler是Android应用程序中负责检查、请求和处理运行时权限结果的组件。根据Android Developer Guide, 2024,权限处理程序将checkSelfPermission、requestPermissions和shouldShowRequestPermissionRationale的逻辑集中到一个类或ViewModel中。这简化了代码维护并改进了测试。
要点
Permission Handler — 是一种用于集中管理Android运行时权限的架构模式。相比于在整个应用程序代码中分散调用ContextCompat.checkSelfPermission和ActivityCompat.requestPermissions,所有请求和结果处理的逻辑都集中在一个类中。这减少了重复,简化了维护,并使代码更具可预测性。
对Permission Handler的需求随着Android 6.0引入运行时权限而产生。在此之前,所有权限都在安装时请求,应用程序代码无需检查即可使用任何API。迁移到运行时模型后,每次使用危险权限都需要三步检查:checkSelfPermission、requestPermissions、onRequestPermissionsResult。这些逻辑分散在Activity和Fragment中会导致内联重复和错误。根据Google I/O 2019的数据,集中处理权限可将Permission Denial相关错误的数量平均减少60%。
一个好的Permission Handler为调用代码提供干净的接口。Activity或Fragment不需要知道请求的细节——它们调用诸如requestCamera(callback)之类的方法,而处理程序自己管理状态检查、显示rationale、调用系统对话框以及将结果传递给回调。这实现了单一责任原则,并将业务逻辑与权限的平台代码分离开来。
当应用程序使用3个或更多危险权限时,Permission Handler就变得必要了。对于具有单一权限(例如用于QR码扫描仪的相机)的简单应用程序,可以直接调用。但对于典型的具有相机、地理位置、通知和存储的移动应用程序,集中式的处理程序对于可维护性是必需的。
典型的Permission Handler由三层组成:接口契约、带有ActivityResultLauncher的实现和ViewModel层。接口定义了每个权限的请求方法——requestCamera、requestLocation、requestStorage。实现将这些方法与相应的ActivityResultContracts.RequestPermission契约关联起来。
架构的关键组件:
这种架构允许在测试中轻松替换实现:使用mock代替真实的ActivityResultLauncher,它返回预定义的结果而无需与系统交互。这对于UI逻辑的单元测试至关重要,因为在这种情况下无法启动Activity来显示权限对话框。
Permission Handler必须考虑Activity和Fragment的生命周期。启动器在ActivityResultRegistry中注册,该注册表在屏幕旋转和Activity重新创建时自动保存和恢复状态。Handler不应持有Activity或Fragment的直接引用——而是使用WeakReference或通过构造函数传递注册表。这可以防止内存泄漏和配置更改时的崩溃。
基本实现Permission Handler基于ActivityResultContracts.RequestPermission构建。Handler从ComponentActivity或Fragment获取ActivityResultRegistry,并为每个权限注册启动器。每个启动器接受一个lambda回调,该回调在用户响应后被调用。
sealed class PermissionResult {
object GRANTED : PermissionResult()
data class DENIED(
val shouldShowRationale: Boolean
) : PermissionResult()
}
interface PermissionHandler {
fun requestCamera(
callback: (PermissionResult) -> Unit
)
fun requestLocation(
callback: (PermissionResult) -> Unit
)
fun isPermissionGranted(
permission: String
): Boolean
}
class AndroidPermissionHandler(
private val registry: ActivityResultRegistry,
private val context: Context
) : PermissionHandler {
private var cameraLauncher: ActivityResultLauncher<String>? = null
fun initialize() {
cameraLauncher = registry.register(
"camera_permission",
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
pendingCameraCallback?.invoke(
PermissionResult.GRANTED
)
} else {
val rationale = ActivityCompat.shouldShowRequestPermissionRationale(
context as Activity,
Manifest.permission.CAMERA
)
pendingCameraCallback?.invoke(
PermissionResult.DENIED(rationale)
)
}
}
}
private var pendingCameraCallback:
((PermissionResult) -> Unit)? = null
override fun requestCamera(
callback: (PermissionResult) -> Unit
) {
if (isPermissionGranted(
Manifest.permission.CAMERA
)) {
callback.invoke(PermissionResult.GRANTED)
return
}
pendingCameraCallback = callback
cameraLauncher?.launch(
Manifest.permission.CAMERA
)
}
override fun isPermissionGranted(
permission: String
): Boolean {
return ContextCompat.checkSelfPermission(
context, permission
) == PackageManager.PERMISSION_GRANTED
}
}
Handler在Activity的onCreate中通过registerForActivityResult进行初始化,这提供了对ActivityResultRegistry的访问权限。初始化后,handler就准备好在Activity的整个生命周期中处理请求。重要的是在第一次请求之前调用initialize,否则启动器将不会被注册。
Permission Handler与ViewModel的集成是最先进的方法。ViewModel管理请求的状态,而Handler仅执行平台调用。ViewModel包含StateFlow<PermissionUiState>,其中UiState描述了正在请求哪个权限以及获得了什么结果。Activity订阅此StateFlow并将请求委托给Handler。
class PermissionsViewModel : ViewModel() {
private val _uiState =
MutableStateFlow<PermissionUiState>(
PermissionUiState.Idle
)
val uiState: StateFlow<PermissionUiState> = _uiState.asStateFlow()
fun onCameraRequested() {
_uiState.value = PermissionUiState.RequestingCamera
}
fun onPermissionResult(
permission: String,
result: PermissionResult
) {
when (result) {
PermissionResult.GRANTED -> {
_uiState.value = PermissionUiState.Granted(permission)
}
is PermissionResult.DENIED -> {
_uiState.value = PermissionUiState.Denied(
permission,
result.shouldShowRationale
)
}
}
}
}
sealed class PermissionUiState {
object Idle : PermissionUiState()
object RequestingCamera : PermissionUiState()
data class Granted(val permission: String) : PermissionUiState()
data class Denied(
val permission: String,
val shouldShowRationale: Boolean
) : PermissionUiState()
}
在这个模型中,Activity在启动时通过Handler检查isPermissionGranted,而ViewModel仅管理状态。如果权限未被授予——Activity订阅uiState,从Handler调用requestCamera,并通过onPermissionResult将结果返回给ViewModel。平台代码和业务逻辑的分离使得可以在没有Android依赖的情况下测试ViewModel。
单元测试Permission Handler得益于PermissionHandler接口成为可能。在测试中,创建FakePermissionHandler来模拟不同的场景:权限已授予、已拒绝、Never Ask Again。每个场景都独立测试。这对于必须正确响应所有三种结果的UI逻辑测试尤为重要。
class FakePermissionHandler : PermissionHandler {
var cameraResult: PermissionResult =
PermissionResult.GRANTED
var grantedPermissions: Set<String> =
setOf(Manifest.permission.CAMERA)
override fun requestCamera(
callback: (PermissionResult) -> Unit
) {
callback.invoke(cameraResult)
}
override fun isPermissionGranted(
permission: String
): Boolean {
return permission in grantedPermissions
}
}
Fake实现允许在没有模拟器的情况下测试ViewModel。只需将cameraResult设置为所需的值,并检查ViewModel是否正确更新了UiState。集成测试使用ActivityScenario测试真实的PermissionHandler,但通常整个应用程序只有2-3个这样的测试——其余场景由使用fake的单元测试覆盖。
使用Permission Handler时的常见错误包括:每次API调用前缺少checkSelfPermission检查、忽略shouldShowRequestPermissionRationale、在Never Ask Again时重复调用requestPermissions以及未考虑Activity生命周期存储启动器。让我们看看每个问题及其解决方案。
最常见的错误——在未检查权限状态的情况下调用API。开发人员假设如果权限曾经被授予,它将永远存在。但是,用户可以随时通过设置撤销权限。Permission Handler在执行敏感操作之前必须始终调用isPermissionGranted。第二个常见错误——忽略shouldShowRequestPermissionRationale并在Never Ask Again时重复请求,导致立即拒绝而没有对话框。
最佳实践包括:为Activity的整个生命周期创建一个Handler实例、使用SharedFlow将结果传递给ViewModel、记录所有请求和拒绝用于分析,以及在第一次拒绝时在系统对话框之前显示自定义rationale对话框。遵循这些规则可确保在所有Android版本上与权限稳定协作。
常见问题
Permission Handler — 用于集中管理运行时权限的组件,封装了checkSelfPermission、requestPermissions和shouldShowRequestPermissionRationale。它简化了代码维护并改进了测试。
推荐使用androidx.activity库中的ActivityResultContracts.RequestPermission。它取代了已弃用的onRequestPermissionsResult,并提供了具有Boolean结果的干净回调API。
对于单个权限,Handler不是必需的——可以直接在Activity中使用RequestPermission启动器。当有3个或更多权限时,Handler变得必要,以避免代码重复。
创建PermissionHandler接口及其用于单元测试的fake实现。Fake在没有系统调用的情况下返回预定义的结果。这允许在没有模拟器的情况下测试ViewModel和UI逻辑。
拒绝后,检查shouldShowRequestPermissionRationale。如果该方法返回false——Never Ask Again模式已激活。Handler应返回PermissionResult.DENIED(false),UI应显示进入设置的按钮。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。