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绘制流程与原理详解
相关推荐
Kapaseker44 分钟前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴1 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭11 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab12 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe17 小时前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少1 天前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker1 天前
一杯美式搞定 Kotlin 空安全
android·kotlin
三少爷的鞋1 天前
Android 协程时代,Handler 应该退休了吗?
android
火柴就是我2 天前
让我们实现一个更好看的内部阴影按钮
android·flutter