Android最新动态权限申请工具

文章目录


前言

由于项目开发中经常会用到动态权限申请,所以就干脆写了一个工具类用来动态权限申请,但是发现老版的必须在activity/fragment页面去重写onRequestPermissionsResult()方法来获取权限申请结果,因此单纯的封装checkSelfPermission()方法没有什么意义,而采用注释的方式进行反射来开发,又会造成性能消耗及安全隐患等问题,直到后来发现了新的api-registerForActivityResult ,这个api是AndroidX库中用来进行跳转后获取返回结果,此外还有申请权限等功能,而且使用过程也是高度解耦的,由此产生了利用该api制作一个工具类的想法,才产生了这篇文章。

这个工具类不仅包含了activity跟fragment的支持,还根据日常使用场景设置了三个可选项,分别是:传入标题与正文后使用预设的dialog提示用户暂时拒绝后的场景;使用自定义的提示框提示用户暂时拒绝后的场景;不进行用户暂时拒绝后的提示,同时将初次对权限检测后不通过的权限传递出来方便日志追踪,也便于对特殊权限不进行申请而是直接跳转系统设置页,以及权限申请之后被暂时拒绝的权限、全部授予的权限、永久拒绝的权限也会传递出来,便于调用时进行下一步设置。

目前该工具类已经进一步优化为依赖库并进行注册,可以通过添加依赖的方式使用,具体内容可参考另一篇文章:Android最新动态权限申请框架YbPermissions


工具类

完善各功能之后的初版代码,唯一缺点是代码集中在一两个类中导致代码臃肿,但是如果不想使用第三方库,而是如同工具类般直接复制代码使用,能够对代码自由调整,则可以使用该版本,已经满足了上面提到的所有的需求,但后续的调整优化只会更新到依赖库中。

1.可直接复制的全部代码

kotlin 复制代码
1.数据类:PermissionEntry
/**
 * 权限获取方式
 * Request:通过系统调用发起权限申请
 * SystemSettings:跳转到设置页让用户手动开启
 */
enum class PermissionRequestMode {
     Request, //正常申请权限
     SystemSettings //跳转到系统页面让用户手动开启
}
/**
 * 表示当前权限的不同状态
 */
enum class PermissionState {
    Granted, //权限已授予
    Denied, //权限被拒绝过且未选择"不在询问" 或检测后没有授权
    PermanentlyDenied //权限被拒绝且勾选"不在询问"
}
/**
 * 存放使用时不同选择的dialog类型
 */
sealed class DialogShow {
    object DialogNone : DialogShow()//不展示dialog,不用shouldShowRequestPermissionRationale来检测
    data class DialogDefault(val dialogTitle: String = "权限申请", val dialogMessage: String = "需要获取权限才能正常使用功能") :
        DialogShow() //使用默认的dialog
    object DialogCustom : DialogShow() //使用自定义的dialog
}

/**
 * 使用数据类存储发起的权限状态
 */
data class PermissionResult(
    val states: Map<String, PermissionState> //存储权限名与对应的状态
) {
    val grantedPermission: Boolean //当前全部权限是否都授予
        get() = states.values.all { it == PermissionState.Granted }
    val deniedPermission: List<String> //被拒绝一次且没有勾选不在询问的权限
        get() = states.filterValues { it == PermissionState.Denied }.keys.toList()
    val permanentlyDeniedPermission: List<String> //储存被拒绝后且勾选不在询问的权限
        get() = states.filterValues { it == PermissionState.PermanentlyDenied }.keys.toList()
}
kotlin 复制代码
2.manager类:
class PermissionManager {
    companion object { //外部通过该静态方法获取Builder实例
        fun builder() = Builder()
    }

    private var activity: WeakReference<ComponentActivity>? = null
    private var fragment: WeakReference<Fragment>? = null

    fun init(fragment: Fragment): PermissionManager {
        this.fragment = WeakReference(fragment)
        this.activity = null
        return this
    }

    fun init(activity: ComponentActivity): PermissionManager {
        this.activity = WeakReference(activity)
        fragment = null
        return this
    }

    //用于接收权限被暂时拒绝后的自定义提示
    private var dialogCallback: ((proceed: () -> Unit) -> Unit)? = null

    //定义发起请求检测的授权情况的结果回调
    private var resultCallback: ((PermissionResult) -> Unit)? = null

    //定义一个变量用来表示是否开启shouldShow的判断
    private var dialogShow: DialogShow =
        DialogShow.DialogDefault() //使用默认的dialog



    //定义launch
    private val launcher: ActivityResultLauncher<Array<String>> by lazy {

        val call = activity?.get() ?: fragment?.get()
        ?: throw IllegalStateException("请先调用 init(Activity/Fragment) 初始化载体")

        // 校验生命周期:禁止在 STARTED 后注册
        val lifecycle = when (call) {
            is Fragment -> call.lifecycle
            is ComponentActivity -> call.lifecycle
            else -> throw IllegalStateException("不支持的载体类型")
        }
        if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
            throw IllegalStateException("请在 onStart()生命周期或之前调用 init 方法,禁止在页面可见后初始化")
        }

        call.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->//请求结果的回调

            val states = mutableMapOf<String, PermissionState>() //收集申请后的结果并用于PermissionResult类创建
            val deniedList = mutableListOf<String>() //收集被拒绝过一次且没有勾选不在询问的权限,便于后续重新发起申请
            //检测返回的权限是否通过
            result.forEach { (permission, granted) -> //权限名,是否获取权限
                if (granted) { //授权
                    Log.i("PermissionManager", "launcher: 已经同意的权限是:$permission")
                    states[permission] = PermissionState.Granted
                } else { //未授权
                    Log.i(
                        "PermissionManager",
                        "launcher: 没有同意的权限是:$permission,dialogShow=${dialogShow}"
                    )
                    states[permission] = PermissionState.Denied
                    if (dialogShow == DialogShow.DialogNone) { //不展示的时候被拒绝权限当作暂时拒绝,供后期直接跳转
                        Log.i("PermissionManager", "launcher: 权限被拒绝且不展示dialog")
                        return@forEach
                    }

                    //展示dialog时会用shouldShowRequestPermissionRationale来细分权限状态
                    dialogPermissionClassify(states, permission, deniedList)

                }
            }
            Log.i("PermissionManager", "deniedList: ${deniedList.toString()}")
            if (deniedList.isNotEmpty()) {
                when (dialogShow) {
                    is DialogShow.DialogDefault -> { //默认显示预设的dialog
                        Log.i("PermissionManager", "dialogShow:DialogDefault ")
                        showDialogUi(deniedList.toTypedArray())
                    }

                    is DialogShow.DialogCustom -> { //自定义显示自定义的dialog
                        Log.i("PermissionManager", "dialogShow:DialogCustom ")

                        showCustomDialog(deniedList)
                    }

                    else -> {
                        Log.i("PermissionManager", "dialogShow:None")

                    } //不展示提示也不会自动发起申请
                }
            }
            resultCallback?.invoke(PermissionResult(states)) //传递授权结果
        }
    }

    /**
     * 将申请后未授权的权限通过shouldShowRequestPermissionRationale返回值来分类为暂时拒绝或永久拒绝
     */
    private fun dialogPermissionClassify(
        states: MutableMap<String, PermissionState>,
        permission: String,
        deniedList: MutableList<String>
    ) {

        val canRequestAgain =
            when (val target = fragment?.get() ?: activity?.get()) {

                is Fragment -> {
                    target.shouldShowRequestPermissionRationale(permission)
                }

                is ComponentActivity -> {
                    target.shouldShowRequestPermissionRationale(permission)
                }

                else -> {
                    throw IllegalStateException("请先调用 init(Activity/Fragment) 初始化载体")
                }
            }
        Log.i(
            "PermissionManager",
            "dialogPermissionClassify: deniedList=${deniedList.toString()},权限=$permission,状态为$canRequestAgain"
        )
        states[permission] = if (canRequestAgain) { //已拒绝未勾选不在询问
            if (deniedList.contains(permission)) { // 去重
                return
            }
            Log.i("PermissionManager", "dialogPermissionClassify: ")
            deniedList.add(permission)
            PermissionState.Denied //暂时拒绝的状态标记
        } else { //已拒绝且勾选不在询问
            PermissionState.PermanentlyDenied //永久拒绝的状态标记
        }
    }

    /**
     * 自定义dialog的展示与发起授权
     */
    private fun showCustomDialog(deniedList: MutableList<String>) {
        dialogCallback?.invoke {
            Log.i("PermissionManager", "showCustomDialog: ")
            launcher.launch(deniedList.toTypedArray())
        } ?: throw NullPointerException("使用自定义dialog时请添加.setDialogCustomCallback()方法")
    }

    /**
     * 预设dialog的展示与发起授权
     */
    private fun showDialogUi(permission: Array<String>) {


        if (dialogShow is DialogShow.DialogDefault) {
            val (title, message) = dialogShow as DialogShow.DialogDefault
            Log.i("PermissionManager", "showDialogUi: dialogTitle=$title,dialogMessage=$message")

            AlertDialog.Builder(requireContext())
                .setTitle(title)
                .setMessage(message)
                .setPositiveButton("开启") { dialog, _ ->
                    dialog.dismiss()
                    launcher.launch(permission)
                }.setNegativeButton("取消") { dialog, _ ->
                    dialog.dismiss()

                }
                .create()
                .show()
        }
    }

    /**
     * 发起权限申请流程
     * permissions:申请的权限数组
     * dialogShow:选择哪种样式展示用户暂时拒绝后的dialog提示,Dialog_None(不展示) Dialog_Default(使用默认样式) Dialog_Custom(使用自定义样式)
     * permissionRoute:使用哪种方式进行权限申请,Request(系统发起权限申请) SystemSettings(跳转设置页由用户手动开启)
     * dialogCallback:使用自定义dialog提示权限暂时拒绝时的额外回调
     * rationale:用于传递初次检测后返回没有授权的权限,同时可在函数逻辑中自定义需要的弹窗,确定按钮中调用proceed()即可发起权限申请或跳转对应设置页
     * resultCallback:权限授予结果返回
     *
     */
    fun request(
        permissions: Array<out String>,
        dialogShow: DialogShow,
        permissionRoute: PermissionRequestMode,
        dialogCallback: ((proceed: () -> Unit) -> Unit)?,
        rationale: ((deniedPermission: List<String>, proceed: () -> Unit) -> Unit)?,
        resultCallback: ((PermissionResult) -> Unit)?
    ): PermissionManager {
        this.resultCallback = resultCallback
        this.dialogShow = dialogShow
        this.dialogCallback = dialogCallback

        //检测
        val stateMap = checkPermissionStates(permissions)
        if (stateMap.values.all { //全部授权则返回
                it == PermissionState.Granted
            }) {
            resultCallback?.invoke(PermissionResult(stateMap))
            return this
        }

        val deniedPermission = stateMap.filterValues { it == PermissionState.Denied }.keys.toList()
        when (permissionRoute) {
            PermissionRequestMode.SystemSettings -> { //直接跳转系统设置页面
                Log.i("PermissionManager", "SystemSettings:跳转系统设置 ")

                rationale?.invoke(deniedPermission) {
                    Log.i("PermissionManager", "rationale已实现")
                    startSettings()
                } ?: run {
                    Log.i("PermissionManager", "rationale未实现")
                    startSettings()
                }
            }

            else -> { //发起权限申请
                Log.i(
                    "PermissionManager",
                    "Request:发起权限申请,deniedPermission=$deniedPermission "
                )
                rationale?.invoke(deniedPermission) {
                    Log.i("PermissionManager", "进行权限申请 ")
                    launcher.launch(deniedPermission.toTypedArray())
                } ?: run { //不设置的时候根据是否有未授权来直接发起权限申请
                    if (deniedPermission.isNotEmpty()) {
                        launcher.launch(deniedPermission.toTypedArray())
                    }
                }
            }
        }
        return this
    }

    /**
     *  @describe: 跳转系统设置页去开通权限
     *  @params:
     *  @return:
     */
    private fun startSettings(){
        requireContext().startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
            setData(
                Uri.fromParts(
                    "package",
                    requireContext().packageName,
                    null
                )
            ) //注意就是"package",不用改成自己的包名
        })
    }

    /**
     * 检测所有的权限是否被授予
     */
    private fun checkPermissionStates(permissions: Array<out String>): Map<String, PermissionState> {
        val stateMap = mutableMapOf<String, PermissionState>()
        permissions.forEach {
            val granted =
                ContextCompat.checkSelfPermission(
                    requireContext(),
                    it
                ) == PackageManager.PERMISSION_GRANTED
            if (granted)
                stateMap[it] = PermissionState.Granted
            else
                stateMap[it] = PermissionState.Denied
        }
        return stateMap

    }

    /**
     * 获取activity/fragment中context
     */
    private fun requireContext(): Context {
        return fragment?.get()?.context           // Fragment 依附时拿宿主 Activity
            ?: activity?.get()?.takeIf { !it.isFinishing }
            ?: throw IllegalStateException("PermissionManager not initialized or activity isFinishing")
    }


    /**
     * Builder类用来包装使用入口
     */
    class Builder {
        private val permissionManager = PermissionManager() //获取pm类实例
        private lateinit var permissions: Array<out String> //获取设置的权限数组
        private var dialogShow: DialogShow = DialogShow.DialogDefault() //默认暂时拒绝后展示dialog提示
        private var permissionRoute: PermissionRequestMode = PermissionRequestMode.Request //默认使用发起权限申请方式
        private var dialogCallback: ((proceed: () -> Unit) -> Unit)? = null //自定义dialog接口
        private var rationale: ((deniedPermission: List<String>, () -> Unit) -> Unit)? =
            null //检测权限后返回没有授权的权限,且必须设置rationale{permissions,proceed -> }中proceed()执行申请权限操作
        private var resultCallback: ((PermissionResult) -> Unit)? = null //发起权限申请后权限的状态

        fun with(activty: ComponentActivity) =
            apply { permissionManager.init(activty) } //获取外部的activity

        fun with(fragment: Fragment) = apply { permissionManager.init(fragment) } //获取外部的fragment
        fun permissions(vararg permissions: String) = apply {
            this.permissions = permissions
        }

        //设置检测后权限的开启方式
        fun permissionRoute(permissionRoute: PermissionRequestMode) =
            apply { this.permissionRoute = permissionRoute }

        //获取检测后的未开通权限,调用后需要用proceed()执行后续逻辑
        fun rationale(rationale: (deniedPermission: List<String>, proceed: () -> Unit) -> Unit) =
            apply { this.rationale = rationale }

        //设置暂时拒绝后的dialog样式
        fun dialogShow(dialogShow: DialogShow) =
            apply { this.dialogShow = dialogShow }

        //自定义dialog样式时用于执行再次发起申请的操作
        fun dialogCallback(dialogCallback: (proceed: () -> Unit) -> Unit) = apply {
            this.dialogCallback = dialogCallback
        } //获取外部的dialogCallback

        fun resultCallback(resultCallback: (PermissionResult) -> Unit) =
            apply { this.resultCallback = resultCallback } //获取外部的resultCallback

        /**
         * 供外部最后发起权限流程
         */
        fun request() {
            permissionManager.request(
                permissions,
                dialogShow,
                permissionRoute,
                dialogCallback,
                rationale,
                resultCallback
            )
        }
    }
}

2.使用的方式

  • 默认的最简实现方式
kotlin 复制代码
  PermissionManager.builder().with(this)
            //设置申请权限
            .permissions(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
            //获取每次发起申请后的权限状态
            .resultCallback{ permissionResult -> //存放每次权限申请后的权限状态
                if(permissionResult.grantedPermission){
                    Log.i("TestActivity", "initData: 权限已全部授权")
                }
                if(permissionResult.permanentlyDeniedPermission.isNotEmpty()){
                    Log.i("TestActivity", "initData: 用户永久拒绝的权限是:${permissionResult.permanentlyDeniedPermission}")
                }
                Log.i("TestActivity", "initData:用户初次拒绝的权限是:${permissionResult.deniedPermission} ")
            }
            .request() //发起权限申请
  • 在权限检测后对申请方式进行拦截选择
kotlin 复制代码
检测后未开通则发起权限申请:
  PermissionManager.builder().with(this)
            //设置申请权限
            .permissions(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
            //1.设置权限检测后开通权限的方式:发起权限申请(默认),跳转系统设置页
            .permissionRoute(PermissionRequestMode.Request)
            //2.获取权限检测后的为开通权限,并设置开通(不需要获取没开通的权限时可省略该方法)
            .rationale{ deniedPermission, proceed ->
                Log.i("TestActivity", "initData:当前没有开通的权限是=$deniedPermission")
                proceed() //如果想要获取未开通权限只能调用该方法,而一旦调用必须设置proceed()才能继续执行下去
            }
            //获取每次发起申请后的权限状态
            .resultCallback{ permissionResult -> //存放每次权限申请后的权限状态
                if(permissionResult.grantedPermission){
                    Log.i("TestActivity", "initData: 权限已全部授权")
                }
                if(permissionResult.permanentlyDeniedPermission.isNotEmpty()){
                    Log.i("TestActivity", "initData: 用户永久拒绝的权限是:${permissionResult.permanentlyDeniedPermission}")
                }
                Log.i("TestActivity", "initData:用户初次拒绝的权限是:${permissionResult.deniedPermission} ")
            }
            .request() //发起权限申请
kotlin 复制代码
直接跳转系统权限开通页:
  //1.设置权限检测后开通权限的方式:发起权限申请(默认),跳转系统设置页
.permissionRoute(PermissionRequestMode.SystemSettings)  //系统设置页
  //2.获取权限检测后的为开通权限,并设置开通
 /* .rationale{ deniedPermission, proceed ->
       Log.i("TestActivity", "initData:当前没有开通的权限是=$deniedPermission")
   proceed() //如果想要获取未开通权限只能调用该方法,而一旦调用必须设置proceed()才能继续执行下去
   }*/
kotlin 复制代码
检测完权限后自定义权限去向:
 //1.设置权限检测后开通权限的方式:发起权限申请(默认),跳转系统设置页
   // .permissionRoute(PermissionRequestMode.SystemSettings)
 //2.获取权限检测后的为开通权限,并设置开通
   .rationale{ deniedPermission, proceed ->
       Log.i("TestActivity", "initData:当前没有开通的权限是=$deniedPermission")
  //不调用proceed,自定义跳转定位开通页,也可在此处添加弹窗提示
                this.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS).apply {
              flags = Intent.FLAG_ACTIVITY_NEW_TASK
                })
            }
  • 权限申请被用户首次拒绝后进行拦截选择
kotlin 复制代码
权限被用户首次拒绝后不提示,也不会进行申请等其他操作
 PermissionManager.builder().with(this)
            //设置申请权限
            .permissions(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
           //设置用户初次拒绝后的提示样式
            .dialogShow(DialogShow.DialogNone) //无提示
            //获取每次发起申请后的权限状态
            .resultCallback{ permissionResult -> //存放每次权限申请后的权限状态
                if(permissionResult.grantedPermission){
                    Log.i("TestActivity", "initData: 权限已全部授权")
                }
                if(permissionResult.permanentlyDeniedPermission.isNotEmpty()){
                    Log.i("TestActivity", "initData: 用户永久拒绝的权限是:${permissionResult.permanentlyDeniedPermission}")
                }
                Log.i("TestActivity", "initData:用户初次拒绝的权限是:${permissionResult.deniedPermission} ")
            }
            .request() //发起权限申请
kotlin 复制代码
用户初次拒绝后设置默认样式的提示:
 //设置用户初次拒绝后的提示样式
.dialogShow(DialogShow.DialogDefault()) //默认的样式
// .dialogShow(DialogShow.DialogDefault("标题","自定义正文")) //添加默认样式的内容
kotlin 复制代码
用户初次拒绝后进行自定义样式的提示
  .dialogShow(DialogShow.DialogCustom) //自定义样式
  .dialogCallback { proceed -> //执行发起权限申请的方法
                AlertDialog.Builder(this)
                    .setMessage("自定义内容")
                    .setPositiveButton("开启") { dialog, _ ->
                        proceed() //发起权限申请,也可以不调用该方法,自定义跳转目标
                        dialog.dismiss()
                    }.setNegativeButton("取消") { dialog, _ ->
                        dialog.dismiss()
                    }
                    .create()
                    .show()
            }

总结

这篇主要介绍了最初编写的工具类使用方式,如果没有特殊需求还是推荐使用后面优化的依赖库进行使用。https://blog.csdn.net/yu537476/article/details/156463276

相关推荐
lxysbly1 天前
psp模拟器安卓版下载汉化版2026
android
2501_941822751 天前
面向灰度发布与风险隔离的互联网系统演进策略与多语言工程实践分享方法论记录思考汇总稿件
android·java·人工智能
触想工业平板电脑一体机1 天前
【触想智能】工业视觉设备与工控一体机进行配套需要注意的五大事项
android·大数据·运维·电脑·智能电视
Android-Flutter1 天前
android compose PullToRefreshAndLoadMore 下拉刷新 + 上拉加载更多 使用
android·kotlin
似霰1 天前
HIDL Hal 开发笔记4----Passthrough HALs 实例分析
android·framework·hal
louisgeek1 天前
Git 查询 Tag 列表
android
诸神黄昏EX1 天前
Android Safety 系列专题【篇二:keystore安全架构】
android
撩得Android一次心动1 天前
Android 架构模式的演变(MVC、MVP、MVVM、MVI)
android·架构·mvc·mvvm·mvp
与水同流1 天前
GNSS数据格式
android