Android 动态权限——添加拦截器

一、动态权限封装与拦截器架构

1. 权限拦截器核心设计(示例代码)

拦截器目标:在权限申请前增加统一拦截逻辑(如展示使用说明弹窗),提升用户授权率。

kotlin 复制代码
// 权限拦截器接口
interface PermissionInterceptor {
    fun shouldIntercept(context: Context, permissions: List<String>): Boolean
    fun intercept(context: Context, permissions: List<String>, callback: () -> Unit)
}

// 示例:首次申请权限时展示头部弹窗的拦截器
class HeaderTipInterceptor : PermissionInterceptor {
    private val sp = context.getSharedPreferences("permission_prefs", MODE_PRIVATE)

    override fun shouldIntercept(context: Context, permissions: List<String>): Boolean {
        return permissions.any { !sp.getBoolean(it, false) }
    }

    override fun intercept(context: Context, permissions: List<String>, callback: () -> Unit) {
        AlertDialog.Builder(context)
            .setTitle("功能权限说明")
            .setMessage("我们需要以下权限以正常使用功能:\n${formatPermissions(permissions)}")
            .setPositiveButton("去申请") { _, _ ->
                // 标记权限已展示过说明
                permissions.forEach { sp.edit().putBoolean(it, true).apply() }
                callback()
            }
            .setNegativeButton("取消", null)
            .show()
    }

    private fun formatPermissions(permissions: List<String>): String {
        return permissions.joinToString("\n") { when (it) {
            Manifest.permission.CAMERA -> "- 相机:用于拍照和视频通话"
            Manifest.permission.ACCESS_FINE_LOCATION -> "- 位置:提供周边服务推荐"
            else -> "- ${it.substringAfterLast('.')}"
        }}
    }
}

2. 增强版权限工具类(集成拦截器:示例代码)

kotlin 复制代码
object PermissionManager {
    private val interceptors = mutableListOf<PermissionInterceptor>()

    // 添加拦截器
    fun addInterceptor(interceptor: PermissionInterceptor) {
        interceptors.add(interceptor)
    }

    // 执行权限请求(带拦截逻辑)
    fun requestPermissions(
        activity: FragmentActivity,
        permissions: Array<String>,
        onResult: (granted: List<String>, denied: List<String>) -> Unit
    ) {
        val filteredPermissions = permissions.filter { 
            ContextCompat.checkSelfPermission(activity, it) != PackageManager.PERMISSION_GRANTED
        }

        if (filteredPermissions.isEmpty()) {
            onResult(permissions.toList(), emptyList())
            return
        }

        // 检查是否需要拦截
        val interceptor = interceptors.firstOrNull { it.shouldIntercept(activity, filteredPermissions) }
        if (interceptor != null) {
            interceptor.intercept(activity, filteredPermissions) {
                realRequest(activity, filteredPermissions, onResult)
            }
        } else {
            realRequest(activity, filteredPermissions, onResult)
        }
    }

    private fun realRequest(
        activity: FragmentActivity,
        permissions: List<String>,
        onResult: (List<String>, List<String>) -> Unit
    ) {
        val launcher = activity.registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ) { grants ->
            val granted = grants.filter { it.value }.keys.toList()
            val denied = grants.filter { !it.value }.keys.toList()
            onResult(granted, denied)
        }
        launcher.launch(permissions.toTypedArray())
    }
}

二、使用示例

1. 初始化拦截器(示例代码)

kotlin 复制代码
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        PermissionManager.addInterceptor(HeaderTipInterceptor())
        PermissionManager.addInterceptor(BackgroundLocationInterceptor()) // 其他拦截器
    }
}

2. 在Activity中请求权限(示例代码)

kotlin 复制代码
class CameraActivity : AppCompatActivity() {
    private val PERMISSIONS = arrayOf(
        Manifest.permission.CAMERA,
        Manifest.permission.RECORD_AUDIO
    )

    fun openCamera() {
        PermissionManager.requestPermissions(
            activity = this,
            permissions = PERMISSIONS,
            onResult = { granted, denied ->
                if (granted.containsAll(PERMISSIONS.toList())) {
                    startCamera()
                } else {
                    handleDeniedPermissions(denied)
                }
            }
        )
    }

    private fun handleDeniedPermissions(denied: List<String>) {
        if (denied.any { !ActivityCompat.shouldShowRequestPermissionRationale(this, it) }) {
            showGoSettingsDialog()
        } else {
            showRetryDialog()
        }
    }
}

三、注意事项与兼容性处理

1. 版本兼容关键点

Android 版本 处理策略
< 6.0 (API 23) 直接授予清单中声明的权限
6.0-10 (API 23-29) 标准动态权限申请流程
11+ (API 30+) 处理单次授权、后台位置权限
13+ (API 33+) 细化媒体权限(READ_MEDIA_IMAGES)、附近设备权限(NEARBY_WIFI_DEVICES)

2. 特殊权限处理(示例代码)

  • 后台定位权限

    kotlin 复制代码
    class BackgroundLocationInterceptor : PermissionInterceptor {
        override fun shouldIntercept(context: Context, permissions: List<String>): Boolean {
            return permissions.contains(Manifest.permission.ACCESS_BACKGROUND_LOCATION) &&
                ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PERMISSION_GRANTED
        }
    
        override fun intercept(context: Context, permissions: List<String>, callback: () -> Unit) {
            AlertDialog.Builder(context)
                .setTitle("需要后台位置权限")
                .setMessage("请允许应用在后台访问位置,以持续提供导航服务")
                .setPositiveButton("去设置") { _, _ ->
                    context.startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
                        data = Uri.fromParts("package", context.packageName, null)
                    })
                }
                .setNegativeButton("取消", null)
                .show()
        }
    }

3. 用户体验优化

  • 权限分组提示:同一权限组的权限合并说明
  • 智能重试策略:用户拒绝后,根据拒绝次数决定展示说明的频率
  • 场景化申请:按功能触发权限申请(如进入拍照界面时申请相机权限)

四、高级功能扩展

1. 权限申请日志追踪(示例代码)

kotlin 复制代码
class AnalyticsInterceptor : PermissionInterceptor {
    override fun shouldIntercept(context: Context, permissions: List<String>) = false

    override fun intercept(context: Context, permissions: List<String>, callback: () -> Unit) {
        // 上报权限申请事件
        Firebase.analytics.logEvent("permission_request", bundleOf(
            "permissions" to permissions.joinToString(",")
        ))
        callback()
    }
}

2. A/B测试不同提示策略(示例代码)

kotlin 复制代码
class A/BTestInterceptor : PermissionInterceptor {
    private val variant = RemoteConfig.getInstance().getString("permission_ui_variant")

    override fun shouldIntercept(context: Context, permissions: List<String>): Boolean {
        return variant != "control"
    }

    override fun intercept(context: Context, permissions: List<String>, callback: () -> Unit) {
        when (variant) {
            "variant1" -> showMaterialDialog(context, callback)
            "variant2" -> showBottomSheet(context, callback)
        }
    }
}

五、完整兼容性方案实践

1. 版本适配工具类(示例代码)

kotlin 复制代码
object PermissionCompat {
    // 检查是否需要特殊处理
    fun needSpecialHandle(permission: String): Boolean {
        return when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && 
                permission == Manifest.permission.ACCESS_BACKGROUND_LOCATION -> true
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && 
                permission == Manifest.permission.READ_MEDIA_IMAGES -> true
            else -> false
        }
    }

    // 转换旧权限到新权限
    fun convertLegacyPermissions(permissions: Array<String>): Array<String> {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            permissions.map {
                if (it == Manifest.permission.READ_EXTERNAL_STORAGE) {
                    Manifest.permission.READ_MEDIA_IMAGES
                } else {
                    it
                }
            }.toTypedArray()
        } else {
            permissions
        }
    }
}

六、总结

通过 拦截器模式 实现的动态权限管理具备以下优势:

  1. 高可扩展性:轻松添加埋点、A/B测试等新功能
  2. 统一入口:集中处理权限相关逻辑,降低耦合
  3. 用户体验可控:通过拦截器灵活控制提示策略
  4. 兼容性保障:通过版本适配层处理系统差异

建议结合 Jetpack SecurityBiometric API 构建完整的应用安全体系。

更多分享

  1. 一文吃透Kotlin中冷流(Clod Flow)和热流(Hot Flow)
  2. 一文带你吃透Kotlin协程的launch()和async()的区别
  3. 一文带你吃透接口(Interface)结合 @AutoService 与 ServiceLoader 详解
  4. 一文带你吃透Android中显示Intent与隐式Intent的区别
  5. 一文带你吃透Android View绘制流程与原理详解
相关推荐
*才华有限公司*3 分钟前
安卓前后端连接教程
android
氦客30 分钟前
Android Compose中的附带效应
android·compose·effect·jetpack·composable·附带效应·side effect
雨白1 小时前
Kotlin 协程的灵魂:结构化并发详解
android·kotlin
我命由我123451 小时前
Android 开发问题:getLeft、getRight、getTop、getBottom 方法返回的值都为 0
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
Modu_MrLiu1 小时前
Android实战进阶 - 用户闲置超时自动退出登录功能详解
android·超时保护·实战进阶·长时间未操作超时保护·闲置超时
Jeled1 小时前
Android 网络层最佳实践:Retrofit + OkHttp 封装与实战
android·okhttp·kotlin·android studio·retrofit
信田君95271 小时前
瑞莎星瑞(Radxa Orion O6) 基于 Android OS 使用 NPU的图片模糊查找APP 开发
android·人工智能·深度学习·神经网络
tangweiguo030519872 小时前
Kotlin 实现 Android 网络状态检测工具类
android·网络·kotlin
nvvas3 小时前
Android Studio JAVA开发按钮跳转功能
android·java·android studio
怪兽20143 小时前
Android多进程通信机制
android·面试