技术调研
相信每位Android开发者,在项目中或多或少也都使用过一些三方权限申请框架,或者直接自己封装的,常见的权限申请方式或框架:
- PermissionsDispatcher,该框架是基于APT(注解处理器)在编译时生成申请权限的代码,缺点就是只能在Activity 和Fragment中使用,并且APT生成代码会给后期带来APK包体积增大,有时候莫名其妙报红;
- RxPermission 是基于Rxjava的思想,支持链式调用,使用非常简单方便,缺点也是只能在Activity 和Fragment中使用。
- 把申请权限的代码封装在BaseXXX中;
- .................
常见的权限申请框架我就不列举了,这些框架也都基本大同小异,都存在如下缺点:
- 仅能在Activity和Fragment申请权限。
- 代码侵入性强。
基础
AOP 即:Aspect-Oriented Programming,即面向切面编程。AOP就是把涉及到众多模块的某一类问题进行统一管理。 比如:申请权限的逻辑在多个模块中使用,那么AOP可以把申请权限的逻辑做统一管理。
用过Glide图片加载框架都知道Glide是通过Fragment或Activity监控生命周期的,那么我们是否可以如Glide加载图片监控生命周期,也分装一个没有界面的Fragment或Activity做中间层处理权限呢?下面我们一起来实现。
权限处理PermissionActivity
class PermissionActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//一像素
window.setGravity(Gravity.LEFT or Gravity.TOP)
val params = window.attributes
params.x = 0
params.y = 0
params.height = 1
params.width = 1
window.attributes = params
// 获取申请权限数据
permissions = intent.getStringArrayExtra(PARAM_PERMISSION) ?: arrayOf()
requestCode = intent.getIntExtra(PARAM_REQUEST_CODE, PARAM_REQUEST_CODE_DEFAULT)
// 申请权限requestCode不能<0会抛异常
// permissions也不能空
// mIPermissionCallback回调
if (permissions.isEmpty() || requestCode < 0 || mIPermissionCallback == null) {
finish()
return
}
//检查是否已经获取了权限,即用户已经允许的权限
if (PermissionUtils.hasSelfPermissions(this, *permissions)) {
//回调通知用户已经授权
mIPermissionCallback?.granted()
this.finish()
return
}
// 申请权限
ActivityCompat.requestPermissions(this, permissions, requestCode)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// 权限申请成功
if (PermissionUtils.verifyPermissions(*grantResults)) {
mIPermissionCallback?.granted()
finish()
return
}
// 用户拒绝授权,并设置了不再提醒
if (!PermissionUtils.shouldShowRequestPermissionRationale(this, *permissions)) {
mIPermissionCallback?.shouldShowRequestPermissionRationale(*permissions)
finish()
return
}
// 用户拒绝授权
if (!PermissionUtils.verifyPermissions(*grantResults)) {
mIPermissionCallback?.denied()
finish()
return
}
// 用户取消授权
mIPermissionCallback?.cancel()
finish()
}
override fun finish() {
super.finish()
overridePendingTransition(0, 0)
}
private lateinit var permissions: Array<String>
private var requestCode: Int = PARAM_REQUEST_CODE_DEFAULT
companion object {
private const val PARAM_PERMISSION = "param_permission"
private const val PARAM_REQUEST_CODE = "param_request_code"
private const val PARAM_REQUEST_CODE_DEFAULT = -1
private var mIPermissionCallback: IPermissionCallback? = null
@JvmStatic
fun requestPermissionAction(
context: Context, permissions: Array<out String>,
requestCode: Int, callback: IPermissionCallback
) = Intent(context, PermissionActivity::class.java).let {
mIPermissionCallback = callback
it.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
it.putExtra(PARAM_PERMISSION, permissions)
it.putExtra(PARAM_REQUEST_CODE, requestCode)
}.run {
ActivityCompat.startActivity(context, this, null)
}
}
}
PermissionActivity 代码不多,和以前权限申请逻辑一样,第一步判断是否已经申请了权限,如果没有则申请权限,当然这里对于权限的处理不过多的介绍。这样封装PermissionActivity 可以解决仅能在Activity和Fragment申请权限
的问题,但是和前面说的一样代码侵入性强。所以我们开始引入AOP技术解决问题。
下面开始介绍AOP是如何申请权限的,就是使用一个没有Layout的Activity或Fragment 和AOP技术封装,以下是AOP的代码。
@Aspect
class PermissionAspect {
@Pointcut(
"execution(@com.youbesun.perform.annotation.Permission * *(..)) && @annotation(permission)"
)
fun permissionMethod(permission: Permission) {//名字和@annotation(permission)保持一致
}
@Around("permissionMethod(permission)")//名字和@annotation(permission)保持一致
@Throws(Throwable::class)
@SuppressWarnings("unused")
fun permissionAspect(
joinPoint: ProceedingJoinPoint,
permission: Permission
) {//名字和@annotation(permission)保持一致
val obj = joinPoint.getThis()//被Aspect的对象
val context = ContextHelper.findContext(obj) //你可以拿到上下文对象
PermissionActivity.requestPermissionAction(
context,
permission.value,
permission.requestCode,
object : IPermissionCallback {
override fun granted() {
joinPoint.proceed(joinPoint.args)
}
override fun denied() {
handleAction(obj, PermissionDenied::class.java)
}
override fun shouldShowRequestPermissionRationale(vararg permissions: String) {
handleAction(obj, ShouldShowRequestRationale::class.java, *permissions)
}
override fun cancel() {
handleAction(obj, PermissionCancel::class.java)
}
}
)
}
@Throws(RuntimeException::class)
private fun handleAction(
obj: Any,
annotationClass: Class<out Annotation>,
vararg permissions: String
) {
val invokeMethod = findInvokeMethod(obj, annotationClass)
if (invokeMethod != null) {
//用户定义了接收shouldShowRequestPermissionRationale的方法,
// 那么如果方法有返回值,并且是Boolean,那么就是表示是否拦截处理,
// 一般是shouldShowRequestPermissionRationale方法,返回true表示拦截
var isIntercepted = invokeMethod.invoke(obj)
// 如果用户不处理,提示用户那么我们需要跳转系统设置
val isShowRationale = annotationClass == ShouldShowRequestRationale::class.java
isIntercepted = (isIntercepted is Boolean) && !isIntercepted
if (isShowRationale && isIntercepted) {
PermissionUtils.startAndroidSettings(ContextHelper.findContext(obj), *permissions)
}
} else if (annotationClass == ShouldShowRequestRationale::class.java) {
// 用户不定义接收ShouldShowRequestRationale的方法,那么直接默认跳转系统设置
PermissionUtils.startAndroidSettings(ContextHelper.findContext(obj), *permissions)
}
}
private fun findInvokeMethod(obj: Any, annotationClass: Class<out Annotation>): Method? {
var invokeMethod: Method? = null
obj.javaClass.declaredMethods.asSequence().forEach {
if (it.isAnnotationPresent(annotationClass)) {
it.isAccessible = true
invokeMethod = it
return@forEach
}
}
return invokeMethod
}
}
对AOP的处理:
- 通过Aspectjx 对@Permission注解处进行代码的织入;
- 然后通过被织入代码的对象反射调用其他方法,这里的对象是要获取Context环境的,因为我们框架需要通过Context启动权限申请的 Activity,我们定义了几个运行时注解@Permission、@PermissionDenied和@ShouldShowRequestRationale注解,分别通过给开发者对权限处理结果的处理;
- @ShouldShowRequestRationale 需要注意的是被@ShouldShowRequestRationale 注解的方法如果有返回值并且是Boolean类型,那么表示开发者是否拦截权限ShouldShowRequestRationale自己处理,如果没有我们会使用我们自己的处理方式去处理,比如:弹窗让用户选择条状Setttings进行权限的授权。
使用
在root build.gradle引入
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
在app build.gradle引入
apply plugin: 'android-aspectjx'
在需要使用android_permission module 中引入:
implementation 'com.github:android_permission_aop:release'
在需要申请权限的方法上加上注解:
@Permission(Manifest.permission.WRITE_EXTERNAL_STORAGE, requestCode = 200)
fun testPermission() {
KLogUtil.e("testPermission")
}
@PermissionDenied(requestCode = 200)
fun testPermissionDenied() {
KLogUtil.e("testPermissionDenied")
}
@PermissionCancel(requestCode = 200)
fun testPermissionCancel() {
KLogUtil.e("testPermissionCancel")
}
@ShouldShowRequestRationale(requestCode = 200)
fun testPermissionDeniedAndNotNote():Boolean {
KLogUtil.e("testPermissionDeniedAndNotNote")
return true
}
使用方式非常的简单,只要使用注解对需要申请权限的方法之上添加@Permission注解即可,如果需要做其他处理,你可以选填@PermissionDenied、@ShouldShowRequestRationale和@PermissionCancel等注解分别对用户拒绝权限、用户拒绝权限并勾选禁止、用户取消授权,做不同结果进行处理,按照目前来说,这个框架可以说是非常好用的。