文章目录
前言
由于项目开发中经常会用到动态权限申请,所以就干脆写了一个工具类用来动态权限申请,但是发现老版的必须在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