“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天, 点击查看活动详情 ”
简介
自从安卓6.0开始,权限部分发生了改变,将权限分为普通权限和危险权限,对于用户权限危险权限,都需要动态申请,于是出现了一些知名权限请求框架,基本上使用APT技术实现,对项目代码的侵入性较高。今天教大家动手封装一种实现简单,使用方便,对代码侵入性较低的权限请求框架。
AspectJ
介绍权限请求框架之前,我们先简单了解一下框架用到的技术 AspectJ。AspectJ 是一个面向切面编程的框架,简称AOP编程。在不侵入原有代码的基础上,实现代码的插入,日志埋点,性能监控,动态权限控制等等。
涉及概念:
- Pointcut 切入点,
- Advice:Advice是定义在pointcut切入的具体操作。主要有五种通知类型:Before(前置通知),AfterReturning(后置通知),AfterThrowing(异常通知),After(最终通知),Around(环绕通知).
使用配置
- 在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;
}
}
}
}
- 根目录 build.gradle。
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
权限框架
整体架构
下面我们开始进入正题,搭建一款无侵入性的网络请求框架。整体架构如下:
框架的整个流程可以分为四步:
-
用户点击业务操作。
-
AOP通过注解添加切入点拦截操作。
-
AOP主动请求权限申请。
-
根据权限申请结果返回给用户。
实现过程
- 首先定义注解,分别作用于用户操作的方法以及权限申请回调后对应执行方法。
//申请权限 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
- 添加切入点 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,"很抱歉,权限已被取消")
}
- 使用一个透明的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
}
}
}
- 定义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的切面编程的特性实现,整体结构简单易懂,使用起来比较方便,通过注解根据不同的权限结果执行对应的方法,对原有代码的侵入性更低。