#頭條創作挑戰賽#
簡介
自從安卓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的切面程式設計的特性實作,整體結構簡單易懂,使用起來比較友善,通過注解根據不同的權限結果執行對應的方法,對原有代碼的侵入性更低。
歡迎大家學習指導!