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本身就有requestPermissionsonRequestPermissionsResult,我们只需要「包装」一下。

// 给 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管理器,配合ActivityonRequestPermissionsResult来分发结果。

高阶函数:让回调「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()
        }
    }
)

你看,onGrantedonDenied都是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本身是异步的,但回调可能不在主线程。记得用runOnUiThreadlifecycleScope.launch(Dispatchers.Main)确保UI操作在主线程。
  • 处理Activity重建:如果权限请求过程中Activity被重建(比如屏幕旋转),回调可能丢失。我建议用SavedStateHandle保存请求状态,或者用ActivityResultLauncher(它自带生命周期管理)。
  • 不要滥用权限:有些开发者为了省事,一次性请求所有权限。这会让用户觉得你的App「不怀好意」。我建议按需请求,用到了再问。

注意:Android 11(API 30)之后,权限机制有重大变化。比如「仅限一次」权限、权限自动重置等。如果你的DSL封装要兼容新版本,记得处理这些情况。

知识体系总览

下面这张图展示了本章的核心逻辑:

权限管理DSL封装核心架构 扩展函数 给Activity/Context 注入权限请求能力 高阶函数 回调Lambda化 onGranted/onDenied DSL封装 链式调用 可读性强 权限请求DSL requestPermissions { permission() onGranted {} onDenied {} } 权限状态观察(StateFlow / LiveData)

从这张图可以看出,扩展函数和高阶函数是DSL的「地基」,而DSL是面向开发者的「门面」。状态观察则贯穿始终,确保UI层能及时响应权限变化。

小技巧:如果你想让DSL更灵活,可以给PermissionScope添加onEach方法,用来处理每个权限的单独回调。比如:

requestPermissions {
    permission(Manifest.permission.CAMERA) {
        onGranted { /* 相机权限已获取 */ }
        onDenied { /* 相机权限被拒 */ }
    }
    permission(Manifest.permission.RECORD_AUDIO) {
        onGranted { /* 录音权限已获取 */ }
    }
}

这样每个权限都有自己的回调,更精细化了。

好了,关于扩展函数和高阶函数在权限管理中的应用,我就讲到这里。记住,好的封装不是为了炫技,而是让代码更安全、更易维护。你写DSL的时候,多想想「如果三个月后的我来读这段代码,能一眼看懂吗?」——这才是评判封装好坏的标准。