技術(shù)調(diào)研
相信每位Android開發(fā)者,在項(xiàng)目中或多或少也都使用過一些三方權(quán)限申請框架,或者直接自己封裝的,常見的權(quán)限申請方式或框架:
- PermissionsDispatcher,該框架是基于APT(注解處理器)在編譯時(shí)生成申請權(quán)限的代碼,缺點(diǎn)就是只能在Activity 和Fragment中使用,并且APT生成代碼會給后期帶來APK包體積增大,有時(shí)候莫名其妙報(bào)紅;
- RxPermission 是基于Rxjava的思想,支持鏈?zhǔn)秸{(diào)用,使用非常簡單方便,缺點(diǎn)也是只能在Activity 和Fragment中使用。
- 把申請權(quán)限的代碼封裝在BaseXXX中;
- .................
常見的權(quán)限申請框架我就不列舉了,這些框架也都基本大同小異,都存在如下缺點(diǎn):
- 僅能在Activity和Fragment申請權(quán)限。
- 代碼侵入性強(qiáng)。
基礎(chǔ)
AOP 即:Aspect-Oriented Programming,即面向切面編程。AOP就是把涉及到眾多模塊的某一類問題進(jìn)行統(tǒng)一管理。 比如:申請權(quán)限的邏輯在多個(gè)模塊中使用,那么AOP可以把申請權(quán)限的邏輯做統(tǒng)一管理。
用過Glide圖片加載框架都知道Glide是通過Fragment或Activity監(jiān)控生命周期的,那么我們是否可以如Glide加載圖片監(jiān)控生命周期,也分裝一個(gè)沒有界面的Fragment或Activity做中間層處理權(quán)限呢?下面我們一起來實(shí)現(xiàn)。
權(quán)限處理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
// 獲取申請權(quán)限數(shù)據(jù)
permissions = intent.getStringArrayExtra(PARAM_PERMISSION) ?: arrayOf()
requestCode = intent.getIntExtra(PARAM_REQUEST_CODE, PARAM_REQUEST_CODE_DEFAULT)
// 申請權(quán)限r(nóng)equestCode不能<0會拋異常
// permissions也不能空
// mIPermissionCallback回調(diào)
if (permissions.isEmpty() || requestCode < 0 || mIPermissionCallback == null) {
finish()
return
}
//檢查是否已經(jīng)獲取了權(quán)限,即用戶已經(jīng)允許的權(quán)限
if (PermissionUtils.hasSelfPermissions(this, *permissions)) {
//回調(diào)通知用戶已經(jīng)授權(quán)
mIPermissionCallback?.granted()
this.finish()
return
}
// 申請權(quán)限
ActivityCompat.requestPermissions(this, permissions, requestCode)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// 權(quán)限申請成功
if (PermissionUtils.verifyPermissions(*grantResults)) {
mIPermissionCallback?.granted()
finish()
return
}
// 用戶拒絕授權(quán),并設(shè)置了不再提醒
if (!PermissionUtils.shouldShowRequestPermissionRationale(this, *permissions)) {
mIPermissionCallback?.shouldShowRequestPermissionRationale(*permissions)
finish()
return
}
// 用戶拒絕授權(quán)
if (!PermissionUtils.verifyPermissions(*grantResults)) {
mIPermissionCallback?.denied()
finish()
return
}
// 用戶取消授權(quán)
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 代碼不多,和以前權(quán)限申請邏輯一樣,第一步判斷是否已經(jīng)申請了權(quán)限,如果沒有則申請權(quán)限,當(dāng)然這里對于權(quán)限的處理不過多的介紹。這樣封裝PermissionActivity 可以解決僅能在Activity和Fragment申請權(quán)限的問題,但是和前面說的一樣代碼侵入性強(qiáng)。所以我們開始引入AOP技術(shù)解決問題。
下面開始介紹AOP是如何申請權(quán)限的,就是使用一個(gè)沒有Layout的Activity或Fragment 和AOP技術(shù)封裝,以下是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)
// 如果用戶不處理,提示用戶那么我們需要跳轉(zhuǎn)系統(tǒng)設(shè)置
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的方法,那么直接默認(rèn)跳轉(zhuǎn)系統(tǒng)設(shè)置
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注解處進(jìn)行代碼的織入;
- 然后通過被織入代碼的對象反射調(diào)用其他方法,這里的對象是要獲取Context環(huán)境的,因?yàn)槲覀兛蚣苄枰ㄟ^Context啟動權(quán)限申請的 Activity,我們定義了幾個(gè)運(yùn)行時(shí)注解@Permission、@PermissionDenied和@ShouldShowRequestRationale注解,分別通過給開發(fā)者對權(quán)限處理結(jié)果的處理;
- @ShouldShowRequestRationale 需要注意的是被@ShouldShowRequestRationale 注解的方法如果有返回值并且是Boolean類型,那么表示開發(fā)者是否攔截權(quán)限ShouldShowRequestRationale自己處理,如果沒有我們會使用我們自己的處理方式去處理,比如:彈窗讓用戶選擇條狀Setttings進(jìn)行權(quán)限的授權(quán)。
使用
在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'
在需要申請權(quán)限的方法上加上注解:
@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
}
使用方式非常的簡單,只要使用注解對需要申請權(quán)限的方法之上添加@Permission注解即可,如果需要做其他處理,你可以選填@PermissionDenied、@ShouldShowRequestRationale和@PermissionCancel等注解分別對用戶拒絕權(quán)限、用戶拒絕權(quán)限并勾選禁止、用戶取消授權(quán),做不同結(jié)果進(jìn)行處理,按照目前來說,這個(gè)框架可以說是非常好用的。