AOP 技术封装Android权限申请框架

技术调研

相信每位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等注解分别对用户拒绝权限、用户拒绝权限并勾选禁止、用户取消授权,做不同结果进行处理,按照目前来说,这个框架可以说是非常好用的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容