19、扩展函数与高阶函数在权限管理中的应用:权限请求的DSL封装、权限回调的Lambda简化、权限状态的观察
权限管理,说白了就是Android应用跟用户之间的一场「信任博弈」。你问用户要相机权限,用户得琢磨一下:「这App靠谱吗?会不会偷拍我?」
我做了这么多年Android开发,早期写权限请求那叫一个痛苦。每次都要写一堆onRequestPermissionsResult回调,代码散落在Activity各个角落,维护起来简直想骂人。后来接触了Kotlin的扩展函数和高阶函数,才发现——原来权限管理可以写得这么优雅。
为什么权限管理需要DSL?
先想想看,传统的权限请求长什么样:
// 传统写法
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.CAMERA), REQUEST_CODE_CAMERA)
} else {
// 已经有权限了
openCamera()
}
// 然后在 onRequestPermissionsResult 里处理结果
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
when (requestCode) {
REQUEST_CODE_CAMERA -> {
if (grantResults.isNotEmpty()
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openCamera()
} else {
showPermissionDeniedDialog()
}
}
}
}
这段代码有什么问题?我总结三点:
- 代码分散:请求逻辑和结果处理不在同一个地方
- 样板代码多:每个权限都要写类似的判断和回调
- 可读性差:新人看这段代码,得花时间理解流程
而用DSL封装后,代码变成这样:
requestPermissions {
permission(Manifest.permission.CAMERA)
onGranted { openCamera() }
onDenied { showPermissionDeniedDialog() }
}
是不是清爽多了?这就是DSL的魅力——让代码读起来像自然语言。
扩展函数:给Activity「开挂」
Kotlin的扩展函数,说白了就是给现有类添加新方法,不用继承也不用装饰。我特别喜欢用它来封装权限请求,因为Activity本身就有requestPermissions和onRequestPermissionsResult,我们只需要「包装」一下。
// 给 Activity 添加扩展函数
fun Activity.requestPermissions(
block: PermissionScope.() -> Unit
) {
val scope = PermissionScope(this)
scope.block()
scope.execute()
}
// PermissionScope 是 DSL 的接收者
class PermissionScope(private val activity: Activity) {
private val permissions = mutableListOf<String>()
private var onGranted: (() -> Unit)? = null
private var onDenied: (() -> Unit)? = null
fun permission(perm: String) {
permissions.add(perm)
}
fun onGranted(action: () -> Unit) {
onGranted = action
}
fun onDenied(action: () -> Unit) {
onDenied = action
}
fun execute() {
// 检查权限状态
val allGranted = permissions.all {
ContextCompat.checkSelfPermission(activity, it)
== PackageManager.PERMISSION_GRANTED
}
if (allGranted) {
onGranted?.invoke()
} else {
// 请求权限
ActivityCompat.requestPermissions(
activity,
permissions.toTypedArray(),
REQUEST_CODE
)
// 这里需要配合 Activity 的 onRequestPermissionsResult
}
}
}
嗯,这里有个坑要注意。上面的execute方法里,我直接调了requestPermissions,但结果回调怎么处理?
我早期做这个封装时,用了ActivityResultLauncher来解耦。后来发现其实可以用一个全局的ResultCallback管理器,配合Activity的onRequestPermissionsResult来分发结果。
高阶函数:让回调「Lambda化」
传统回调为什么烦人?因为你要实现接口、重写方法,代码量翻倍不说,还容易搞错参数顺序。高阶函数可以把回调变成Lambda,说白了就是「把函数当参数传」。
我举个例子,权限请求的结果处理:
// 高阶函数封装
fun Activity.requestPermission(
permission: String,
onGranted: () -> Unit,
onDenied: (shouldShowRationale: Boolean) -> Unit
) {
if (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
onGranted()
} else {
// 请求权限,并在回调中调用 onGranted 或 onDenied
requestPermissions(arrayOf(permission), REQUEST_CODE)
// 实际项目中需要配合 ActivityResultLauncher
}
}
// 使用
requestPermission(
permission = Manifest.permission.CAMERA,
onGranted = { openCamera() },
onDenied = { shouldShowRationale ->
if (shouldShowRationale) {
showRationaleDialog()
} else {
showSettingsDialog()
}
}
)
你看,onGranted和onDenied都是Lambda,调用时一目了然。我在项目中遇到过一个场景:用户连续拒绝两次后,系统会不再弹出权限对话框。这时候shouldShowRationale就派上用场了——它可以判断是否需要引导用户去设置里手动开启权限。
权限状态的观察:用StateFlow监听变化
权限状态不是一成不变的。用户可能在设置里关闭权限,或者系统更新后重置权限。我建议用StateFlow来观察权限状态,这样UI层可以自动响应变化。
class PermissionObserver(private val context: Context) {
private val _cameraPermissionState = MutableStateFlow(false)
val cameraPermissionState: StateFlow<Boolean> = _cameraPermissionState.asStateFlow()
private val _locationPermissionState = MutableStateFlow(false)
val locationPermissionState: StateFlow<Boolean> = _locationPermissionState.asStateFlow()
fun checkPermissions() {
_cameraPermissionState.value = ContextCompat.checkSelfPermission(
context, Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
_locationPermissionState.value = ContextCompat.checkSelfPermission(
context, Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
// 注册权限变化监听(需要配合 BroadcastReceiver 或 Lifecycle)
fun observe(owner: LifecycleOwner, onChanged: (Boolean) -> Unit) {
cameraPermissionState.observe(owner) { isGranted ->
onChanged(isGranted)
}
}
}
为什么用StateFlow而不是LiveData?我个人习惯用StateFlow,因为它跟Kotlin协程配合更好,而且没有LiveData的「粘性事件」问题。不过如果你项目里已经用了LiveData,也没必要强行换。
完整DSL封装示例
把上面这些整合起来,一个完整的权限DSL大概长这样:
// 最终DSL
requestPermissions {
permission(Manifest.permission.CAMERA)
permission(Manifest.permission.RECORD_AUDIO)
onGranted {
startRecording()
}
onDenied { deniedList ->
showDeniedDialog(deniedList)
}
onPermanentlyDenied { permanentlyDenied ->
showSettingsNavigation(permanentlyDenied)
}
observePermissionState { permission, isGranted ->
updateUI(permission, isGranted)
}
}
这个DSL把权限请求、结果处理、状态观察都整合在一起了。你想想看,如果不用DSL,同样的功能得写多少代码?
核心思路:扩展函数负责「注入」能力,高阶函数负责「传递」行为,DSL负责「组织」代码。三者结合,让权限管理变得像写配置一样简单。
避坑指南
我踩过不少权限相关的坑,分享几个给你:
- 不要在主线程请求权限:虽然
requestPermissions本身是异步的,但回调可能不在主线程。记得用runOnUiThread或lifecycleScope.launch(Dispatchers.Main)确保UI操作在主线程。 - 处理Activity重建:如果权限请求过程中Activity被重建(比如屏幕旋转),回调可能丢失。我建议用
SavedStateHandle保存请求状态,或者用ActivityResultLauncher(它自带生命周期管理)。 - 不要滥用权限:有些开发者为了省事,一次性请求所有权限。这会让用户觉得你的App「不怀好意」。我建议按需请求,用到了再问。
注意:Android 11(API 30)之后,权限机制有重大变化。比如「仅限一次」权限、权限自动重置等。如果你的DSL封装要兼容新版本,记得处理这些情况。
知识体系总览
下面这张图展示了本章的核心逻辑:
从这张图可以看出,扩展函数和高阶函数是DSL的「地基」,而DSL是面向开发者的「门面」。状态观察则贯穿始终,确保UI层能及时响应权限变化。
小技巧:如果你想让DSL更灵活,可以给PermissionScope添加onEach方法,用来处理每个权限的单独回调。比如:
requestPermissions {
permission(Manifest.permission.CAMERA) {
onGranted { /* 相机权限已获取 */ }
onDenied { /* 相机权限被拒 */ }
}
permission(Manifest.permission.RECORD_AUDIO) {
onGranted { /* 录音权限已获取 */ }
}
}
这样每个权限都有自己的回调,更精细化了。
好了,关于扩展函数和高阶函数在权限管理中的应用,我就讲到这里。记住,好的封装不是为了炫技,而是让代码更安全、更易维护。你写DSL的时候,多想想「如果三个月后的我来读这段代码,能一眼看懂吗?」——这才是评判封装好坏的标准。