Android框架之教你亲手打造一款无侵入性的权限框架

266 阅读3分钟

“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天, 点击查看活动详情

简介

自从安卓6.0开始,权限部分发生了改变,将权限分为普通权限和危险权限,对于用户权限危险权限,都需要动态申请,于是出现了一些知名权限请求框架,基本上使用APT技术实现,对项目代码的侵入性较高。今天教大家动手封装一种实现简单,使用方便,对代码侵入性较低的权限请求框架。

AspectJ

介绍权限请求框架之前,我们先简单了解一下框架用到的技术 AspectJ。AspectJ 是一个面向切面编程的框架,简称AOP编程。在不侵入原有代码的基础上,实现代码的插入,日志埋点,性能监控,动态权限控制等等。

涉及概念:

  1. Pointcut 切入点,
  2. Advice:Advice是定义在pointcut切入的具体操作。主要有五种通知类型:Before(前置通知),AfterReturning(后置通知),AfterThrowing(异常通知),After(最终通知),Around(环绕通知).

使用配置

  1. 在app的build.gradle中。
dependencies {  
    implementation 'org.aspectj:aspectjrt:1.8.14'
  } 
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

final def log = project.logger
final def variants = project.android.applicationVariants

variants.all { variant ->

    if (!variant.buildType.isDebuggable()) {
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return;
    }

    def javaCompile = variant.javaCompile

    javaCompile.doLast {

        String[] args = ["-showWeaveInfo",

                         "-1.8",

                         "-inpath", javaCompile.destinationDir.toString(),

                         "-aspectpath", javaCompile.classpath.asPath,

                         "-d", javaCompile.destinationDir.toString(),

                         "-classpath", javaCompile.classpath.asPath,

                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]

        log.debug "ajc args: " + Arrays.toString(args)

        MessageHandler handler = new MessageHandler(true);

        new Main().run(args, handler);

        for (IMessage message : handler.getMessages(null, true)) {

            switch (message.getKind()) {

                case IMessage.ABORT:

                case IMessage.ERROR:

                case IMessage.FAIL:

                    log.error message.message, message.thrown

                    break;

                case IMessage.WARNING:

                    log.warn message.message, message.thrown

                    break;

                case IMessage.INFO:

                    log.info message.message, message.thrown

                    break;

                case IMessage.DEBUG:

                    log.debug message.message, message.thrown

                    break;
            }
        }
    }
}
  1. 根目录 build.gradle。
dependencies {    
   classpath 'org.aspectj:aspectjtools:1.8.9'      
   classpath 'org.aspectj:aspectjweaver:1.8.9' 
      }

权限框架

整体架构

下面我们开始进入正题,搭建一款无侵入性的网络请求框架。整体架构如下:

框架的整个流程可以分为四步:

  1. 用户点击业务操作。

  2. AOP通过注解添加切入点拦截操作。

  3. AOP主动请求权限申请。

  4. 根据权限申请结果返回给用户。

实现过程

  1. 首先定义注解,分别作用于用户操作的方法以及权限申请回调后对应执行方法。
//申请权限 value 对应的是权限集合,requestCode 请求码
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class PermissionRequest(
    val value:Array<String>,val requestCode:Int
)
//权限拒绝
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class PermissionDenied
//权限取消
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class PermissionCancel
  1. 添加切入点 PointCut,在用户操作的方法上添加注解@PermissionRequest,在open方法执行之前进行拦截。同时将其他注解分别作用于不同结果的方法上。
    @PermissionRequest(value = [Manifest.permission.WRITE_EXTERNAL_STORAGE,
                                Manifest.permission.CALL_PHONE],200)
    fun open() {
        ToastUtils.show(this,"恭喜您,已获取权限")
        Log.i("TAGasdf", "open: 恭喜您,已获取权限")
    }
   
    @PermissionDenied
    fun denied(){
        ToastUtils.show(this,"您已拒绝权限,请手动在系统设置中打开权限")
    }
    @PermissionCancel
    fun cancel(){
        ToastUtils.show(this,"很抱歉,权限已被取消")
    }
  1. 使用一个透明的Activity用于真正的权限请求,将需要请求的权限和请求码传递给Activity进行权限请求,根据权限请求结果通过接口回调给AOP。
class PermissionView : BaseActivity<ActivityMainPermissionBinding>() {
    var requestCodeInt = 0
    var permissions :Array<String> ?= null
    override fun getLayoutId(): Int = R.layout.activity_permission_view

    override fun initView() {
        requestCodeInt =  intent.getIntExtra(REQUEST_CODE,0)
       permissions = intent.getStringArrayExtra(PERMISSION)
        if (permissions == null || requestCodeInt < 0 || permissionListener == null){
            this.finish()
            return
        }
        permissions?.let {
          if (PermissionUtils.hasPermissionRequest(this,*it))   {
              permissionListener?.permissionGranted()
              this.finish()
              return
          }
            ActivityCompat.requestPermissions(this, it,requestCodeInt)
        }
    }

    override fun initListener() {

    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCodeInt == requestCode){
           if (PermissionUtils.requestPermissionSuccess(*grantResults)){
               permissionListener?.permissionGranted()
               this.finish()
               return
           }
            if (!PermissionUtils.shouldShowRequestPermissionRationale(this,*permissions)){
                permissionListener?.permissionDenied()
                this.finish()
                return
            }
            permissionListener?.permissionCancel()
            this.finish()
            return
        }
    }
}
  1. 定义AOP拦截类,添加拦截方法,在拦截方法里请求权限,根据权限的返回结果执行不同的注解方法。
 @Throws(Throwable::class)
    @Around("pointAspectJPermission(permissionRequest)")
    fun aProceedingJonPoint(joinPoint: ProceedingJoinPoint,permissionRequest: PermissionRequest){
      val  joinThis  = joinPoint.`this`
        ..........................
        PermissionView.requestPermission(context,permissionRequest.value,permissionRequest.requestCode,object :PermissionListener{
            override fun permissionGranted() {
                try {
                    //如果请求成功,继续执行open方法
                    joinPoint.proceed()
                }catch (e:java.lang.Exception){

                }
            }

            override fun permissionDenied() {
                //权限拒绝 执行PermissionDenied注解
                PermissionUtils.invokeAnnotion(context,PermissionDenied::class.java)
            }

            override fun permissionCancel() {
                //权限取消 执行PermissionDenied注解
                  PermissionUtils.invokeAnnotion(context,PermissionCancel::class.java)
            }

        })
        
    }

本架构使用AOP的切面编程的特性实现,整体结构简单易懂,使用起来比较方便,通过注解根据不同的权限结果执行对应的方法,对原有代码的侵入性更低。