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绘制流程与原理详解
相关推荐
casual_clover1 小时前
Android 设备实现 adb connect 连接的步骤
android·adb
恋猫de小郭1 小时前
Android 确定废弃「屏幕方向锁定」等 API ,如何让 App 适配大屏和 PC/XR 等场景
android·前端·flutter
神仙别闹2 小时前
基于Java(SSM)+Mysql实现移动大厅业务办理(增删改查)
android·java·mysql
木子庆五4 小时前
Android设计模式之模板方法模式
android·设计模式·模板方法模式
孤舟簔笠翁6 小时前
【Audio开发一】android音频问题排查指南
android·音视频
CYRUS_STUDIO8 小时前
Unidbg Trace 反 OLLVM 控制流平坦化(fla)
android·逆向·汇编语言
木子庆五10 小时前
Android设计模式之工厂方法模式
android·设计模式·工厂方法模式
_丿丨丨_11 小时前
PHP回调后门小总结
android·开发语言·php
程序员江同学11 小时前
Kotlin 技术月报 | 2025 年 3 月
android·kotlin
热爱编程的小曾12 小时前
sqli-labs靶场 less 7
android·adb·less