天天看點

Android架構之親手打造一款無侵入性的權限架構

#頭條創作挑戰賽#

簡介

自從安卓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' 
      }           

權限架構

整體架構

下面我們開始進入正題,搭建一款無侵入性的網絡請求架構。整體架構如下:

Android架構之親手打造一款無侵入性的權限架構

架構的整個流程可以分為四步:

  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的切面程式設計的特性實作,整體結構簡單易懂,使用起來比較友善,通過注解根據不同的權限結果執行對應的方法,對原有代碼的侵入性更低。

歡迎大家學習指導!

繼續閱讀