新聞中心
小編給大家分享一下Android如何采用AOP方式封裝6.0權(quán)限管理,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
為大柴旦等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計(jì)制作服務(wù),及大柴旦網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為網(wǎng)站設(shè)計(jì)、成都網(wǎng)站設(shè)計(jì)、大柴旦網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!
【一】背景
6.0運(yùn)行時(shí)申請權(quán)限已經(jīng)是一個(gè)老生常談的內(nèi)容了,最近項(xiàng)目TargetSDKVersion升到23以上,所以我們也需要做權(quán)限管理,我想到的需求是這樣的:
1、支持單個(gè)權(quán)限、多個(gè)權(quán)限申請
2、運(yùn)行時(shí)申請
3、無侵入式申請,無需關(guān)注權(quán)限申請的邏輯
4、除了Activity、Fragment之外,還需要支持Service中申請
5、對國產(chǎn)手機(jī)做兼容處理
第一、二點(diǎn),Google都有對應(yīng)的API;
第三點(diǎn)可以通過自定義注解+AOP切面方式來解決。為什么采用AOP方式呢?首先看AOP定義: 面向切面編程(Aspect-Oriented Programming)。如果說,OOP(面向?qū)ο螅┤绻前褑栴}劃分到單個(gè)模塊的話,那么AOP就是把涉及到眾多模塊的某一類問題進(jìn)行統(tǒng)一管理。 因?yàn)槲覀兩暾垯?quán)限的邏輯都是基本一樣的,所以可以把申請權(quán)限的邏輯統(tǒng)一管理。
第四點(diǎn)稍微有點(diǎn)麻煩,因?yàn)镚oogle提供的API只支持在Activity和Fragment中去申請權(quán)限,Service中并沒有相應(yīng)的API,比如項(xiàng)目中的某個(gè)Service里需要拿到當(dāng)前位置信息,并且不能確定定位權(quán)限已經(jīng)給了,所以在定位之前仍然需要判斷有沒有定位權(quán)限,按照常規(guī)邏輯好像是行不通了。腫么辦呢?先說一下我想到的辦法:通過一個(gè)透明的Activity去申請權(quán)限,并且把申請結(jié)果返回來,最后實(shí)踐也是這么做的,具體思路請往下看。
第五點(diǎn)也比較麻煩,如果都按Google標(biāo)準(zhǔn)來,那就不用考慮兼容問題了,但是國產(chǎn)安卓手機(jī)碎片化比較嚴(yán)重,且基本都修改了ROM,導(dǎo)致申請權(quán)限的API跟期望返回的結(jié)果不一致,這種的可能就需要特殊處理了。
調(diào)研了一下比較流行的三方庫,如PermissionsDispatcher 、RxPermissions ,做了一個(gè)簡單的總結(jié):
| 權(quán)限庫 | 是否使用注解 | 是否支持鏈?zhǔn)秸{(diào)用 | 是否支持Service | 是否適配國產(chǎn)機(jī) |
|---|---|---|---|---|
| RxPermissions | No | Yes | No | No |
| PermissionsDispatcher | Yes | No | No | 適配了小米 |
undefined
【二】效果圖
先上一下最終的效果圖:

效果圖有點(diǎn)模糊,可以下載源碼運(yùn)行一下看效果
【三】整體思路
首先,先定義一個(gè)說法,彈出系統(tǒng)權(quán)限彈窗,用戶沒有給權(quán)限,并且選中不再提示,這種情況稱為權(quán)限被拒絕;如果用戶沒有給權(quán)限,但是沒有選中不再提示,這種情況稱為權(quán)限被取消。申請權(quán)限、權(quán)限被取消、權(quán)限被拒絕都是采用注解的形式,分別為@NeedPermission、@PermissionCanceled、@PermissionDenied,注解都是聲明在Method級(jí)別上的。在我們的Activity、Fragment及Service中聲明注解,然后在AOP中解析我們的注解,并把申請的權(quán)限傳遞給一個(gè)透明的Activity去處理,并把處理結(jié)果返回來。這就是整體思路,可能會(huì)遇到的問題:
1、 不同型號(hào)的手機(jī)兼容問題(申請權(quán)限、跳設(shè)置界面)
2、AOP解析注解以及傳值問題
上面說了很多,其實(shí)用一個(gè)圖來表示更清晰一些:

UML時(shí)序圖.png
OK,通過上面的圖是不是更清晰了呢?其實(shí)最關(guān)鍵的地方就是AOP解析注解及傳值。AOP面向切面編程是一種編程思想,而AspectJ是對AOP編程思想的一個(gè)實(shí)踐,本文采用AspectJ來實(shí)現(xiàn)切面編程,簡單介紹AspectJ的幾個(gè)概念:
JPoint:代碼可注入的點(diǎn),比如一個(gè)方法的調(diào)用處或者方法內(nèi)部,對于本文來說即是注解作用的方法。
Pointcut:用來描述 JPoint 注入點(diǎn)的一段表達(dá)式。見下面例子
Advice:常見的有 Before、After、Around 等,表示代碼執(zhí)行前、執(zhí)行后、替換目標(biāo)代碼,也就是在 Pointcut 何處編織代碼。
Aspect:切面,Pointcut 和 Advice 合在一起稱作 Aspect。
關(guān)于AspectJ的介紹及用法的文章很多,不了解的朋友可以去google下,直接列一下AOP切面代碼:
@Aspect
public class PermissionAspect {
private static final String PERMISSION_REQUEST_POINTCUT =
"execution(@com.ninetripods.aopermission.permissionlib.annotation.NeedPermission * *(..))";
@Pointcut(PERMISSION_REQUEST_POINTCUT + " && @annotation(needPermission)")
public void requestPermissionMethod(NeedPermission needPermission) {
}
@Around("requestPermissionMethod(needPermission)")
public void AroundJoinPoint(final ProceedingJoinPoint joinPoint, NeedPermission needPermission) {
Context context = null;
final Object object = joinPoint.getThis();
if (object instanceof Context) {
context = (Context) object;
} else if (object instanceof Fragment) {
context = ((Fragment) object).getActivity();
} else if (object instanceof android.support.v4.app.Fragment) {
context = ((android.support.v4.app.Fragment) object).getActivity();
}
if (context == null || needPermission == null) return;
PermissionRequestActivity.PermissionRequest(context, needPermission.value(),
needPermission.requestCode(), new IPermission() {
@Override
public void PermissionGranted() {
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void PermissionDenied(int requestCode, List denyList) {
Class> cls = object.getClass();
Method[] methods = cls.getDeclaredMethods();
if (methods == null || methods.length == 0) return;
for (Method method : methods) {
//過濾不含自定義注解PermissionDenied的方法
boolean isHasAnnotation = method.isAnnotationPresent(PermissionDenied.class);
if (isHasAnnotation) {
method.setAccessible(true);
//獲取方法類型
Class>[] types = method.getParameterTypes();
if (types == null || types.length != 1) return;
//獲取方法上的注解
PermissionDenied aInfo = method.getAnnotation(PermissionDenied.class);
if (aInfo == null) return;
//解析注解上對應(yīng)的信息
DenyBean bean = new DenyBean();
bean.setRequestCode(requestCode);
bean.setDenyList(denyList);
try {
method.invoke(object, bean);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
@Override
public void PermissionCanceled(int requestCode) {
Class> cls = object.getClass();
Method[] methods = cls.getDeclaredMethods();
if (methods == null || methods.length == 0) return;
for (Method method : methods) {
//過濾不含自定義注解PermissionCanceled的方法
boolean isHasAnnotation = method.isAnnotationPresent(PermissionCanceled.class);
if (isHasAnnotation) {
method.setAccessible(true);
//獲取方法類型
Class>[] types = method.getParameterTypes();
if (types == null || types.length != 1) return;
//獲取方法上的注解
PermissionCanceled aInfo = method.getAnnotation(PermissionCanceled.class);
if (aInfo == null) return;
//解析注解上對應(yīng)的信息
CancelBean bean = new CancelBean();
bean.setRequestCode(requestCode);
try {
method.invoke(object, bean);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
});
}
} 代碼有點(diǎn)多,但是思路還是挺清晰的,首先定義@Pointcut(描述的是我們的注解@NeedPermission),接著由Advice(@Around)及Pointcut構(gòu)成我們的切面Aspect, 在切面Aspect中,通過joinPoint.getThis()根據(jù)不同來源來獲得Context,接著跳轉(zhuǎn)到一個(gè)透明Activity申請權(quán)限并通過接口回調(diào)拿到權(quán)限申請結(jié)果,最后在不同的回調(diào)方法里通過反射把回調(diào)結(jié)果回傳給調(diào)用方。
【四】使用舉例
為了簡化AspectJ的各種配置,這里用了一個(gè)三方的gradle插件:
https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx
1、權(quán)限庫引入方式,在app模塊的build.gradle中引入如下:
apply plugin: 'android-aspectjx'
dependencies {
compile 'com.ninetripods:aop-permission:1.0.1'
..........其他............
}2、在整個(gè)工程的build.gradle里面配置如下:
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.8'
................其他................
}說明: aspectjx:1.0.8不是最新版本,最高支持gradle的版本到2.3.3,如果你的工程里gradle版本是3.0.0以上,請使用aspectjx:1.1.0以上版本,aspectjx歷史版本查看地址:
https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx/blob/master/CHANGELOG.md
3、如果你的項(xiàng)目里使用了混淆,需要在AOP代碼進(jìn)行hook的類及方法名不能被混淆,即被注解作用的類及方法不能被混淆,需要在混淆配置里keep住, 比如:
package com.hujiang.test;
public class A {
@NeedPermission
public boolean funcA(String args) {
....
}
}
//如果你在AOP代碼里對A#funcA(String)進(jìn)行hook, 那么在混淆配置文件里加上這樣的配置
-keep class com.hujiang.test.A {*;}4、終于配好了,都閃開,我要開始舉栗子了:

下面以Activity中申請權(quán)限為例,F(xiàn)ragment、Service中使用是一樣的,就不一一寫了,源碼中也有相應(yīng)使用的Demo
4.1 申請單個(gè)權(quán)限
申請單個(gè)權(quán)限:
btn_click.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
callMap();
}
});
/**
* 申請權(quán)限
*/
@NeedPermission(value = {Manifest.permission.ACCESS_FINE_LOCATION}, requestCode = 0)
private void callMap() {
Toast.makeText(this, "定位權(quán)限申請通過", Toast.LENGTH_SHORT).show();
}@NeedPermission后面的value代表需要申請的權(quán)限,是一個(gè)String[]數(shù)組;requestCode是請求碼,是為了區(qū)別開同一個(gè)Activity中有多個(gè)不同的權(quán)限請求,默認(rèn)是0,如果同一個(gè)Activity中只有一個(gè)權(quán)限申請,requestCode可以忽略不寫。
/**
* 權(quán)限被取消
*
* @param bean CancelBean
*/
@PermissionCanceled
public void dealCancelPermission(CancelBean bean) {
Toast.makeText(this, "requestCode:" + bean.getRequestCode(), Toast.LENGTH_SHORT).show();
}聲明一個(gè)public方法接收權(quán)限被取消的回調(diào), 方法必須有一個(gè)CancelBean類型的參數(shù),這點(diǎn)類似于EventBus,CancelBean中有requestCode變量,即是我們請求權(quán)限時(shí)的請求碼。
/**
* 權(quán)限被拒絕
*
* @param bean DenyBean
*/
@PermissionDenied
public void dealPermission(DenyBean bean) {
Toast.makeText(this,
"requestCode:" + bean.getRequestCode()+ ",Permissions: " + Arrays.toString(bean.getDenyList().toArray()), Toast.LENGTH_SHORT).show();
}聲明一個(gè)public方法接收權(quán)限被取消的回調(diào), 方法必須有一個(gè)DenyBean類型的參數(shù),DenyBean中有一個(gè)requestCode變量,即是我們請求權(quán)限時(shí)的請求碼,另外還可以通過denyBean.getDenyList()來拿到被權(quán)限被拒絕的List。
4.2 申請多個(gè)權(quán)限
基本用法同上,區(qū)別是@NeedPermission后面聲明的權(quán)限是多個(gè),如下:
/**
* 申請多個(gè)權(quán)限
*/
@NeedPermission(value = {Manifest.permission.CALL_PHONE, Manifest.permission.CAMERA}, requestCode = 10)
public void callPhone() {
Toast.makeText(this, "電話、相機(jī)權(quán)限申請通過", Toast.LENGTH_SHORT).show();
}value中聲明了兩個(gè)權(quán)限,一個(gè)電話權(quán)限,一個(gè)相機(jī)權(quán)限
4.3 跳轉(zhuǎn)到設(shè)置類
當(dāng)用戶拒絕權(quán)限并選中不再提示后,需要引導(dǎo)用戶去設(shè)置界面打開權(quán)限,因?yàn)閲a(chǎn)手機(jī)各個(gè)設(shè)置界面不一樣,用通用的API可能會(huì)跳轉(zhuǎn)不到相應(yīng)的APP設(shè)置界面,這里采用了策略模式(下圖所示)

跳轉(zhuǎn)到設(shè)置類.png
如需做兼容,只需要在庫里修改,調(diào)用方是不需要處理的,調(diào)用方如需跳轉(zhuǎn)到設(shè)置界面,只需像下面這樣調(diào)用就OK了:
【五】總結(jié)
回看一下我們的需求,基本上都實(shí)現(xiàn)了:
1、首先通過@NeedPermission、@PermissionCanceled、@PermissionDenied三個(gè)注解來分別定義權(quán)限申請、被取消、被拒絕三種情況,如果不想處理被取消的邏輯就不用使用@PermissionCanceled注解,其他權(quán)限申請的邏輯調(diào)用方不用關(guān)心,是完全解耦的;
2、同時(shí)支持在Activity、Fragment、Service中去申請權(quán)限;
3、最后關(guān)于申請權(quán)限、跳設(shè)置界面的兼容問題,因?yàn)樯磉叺氖謾C(jī)有限,不能測試出所有兼容問題,需要后續(xù)優(yōu)化。
以上是“Android如何采用AOP方式封裝6.0權(quán)限管理”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
網(wǎng)頁名稱:Android如何采用AOP方式封裝6.0權(quán)限管理
網(wǎng)址分享:http://www.dlmjj.cn/article/jhogic.html


咨詢
建站咨詢
