我們都知道,在Android6.0后,權(quán)限申請需要?jiǎng)討B(tài)授權(quán)處理,才能通過。這樣的設(shè)計(jì)也更加符合現(xiàn)在用戶的安全體驗(yàn)。那么,對于一個(gè)應(yīng)用,我們可能在不同的場景,需要多次申請不同的權(quán)限。比如,在做緩存的時(shí)候,需要申請sd卡讀寫權(quán)限,在拍照上傳圖片的時(shí)候,需要申請拍照權(quán)限等。對于這樣的請求,我們一般是怎樣去處理封裝的呢?
項(xiàng)目地址
一、常見的權(quán)限處理封裝
可以看到,這樣處理并不理想,耦合性還是太高了。
那么,有沒有更加優(yōu)越的方法處理呢?最終到達(dá)請求發(fā)出端,跟請求處理端完全隔離開來呢?方法肯定是有的。這里,所用到的核心就是AOP編程。
二、AOP介紹
1、AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。利用AOP可以對業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時(shí)提高了開發(fā)的效率。
2、它是一種關(guān)注點(diǎn)分離的技術(shù)。我們軟件開發(fā)時(shí)經(jīng)常提一個(gè)詞叫做“業(yè)務(wù)邏輯”或者“業(yè)務(wù)功能”,我們的代碼主要就是實(shí)現(xiàn)某種特定的業(yè)務(wù)邏輯。但是我們往往不能專注于業(yè)務(wù)邏輯,比如我們寫業(yè)務(wù)邏輯代碼的同時(shí),還要寫事務(wù)管理、緩存、日志等等通用化的功能,而且每個(gè)業(yè)務(wù)功能都要和這些業(yè)務(wù)功能混在一起,非常非常地痛苦。為了將業(yè)務(wù)功能的關(guān)注點(diǎn)和通用化功能的關(guān)注點(diǎn)分離開來,就出現(xiàn)了AOP技術(shù)。
3、關(guān)于AOP的語法,可以先去看看,這里就不詳細(xì)說明了。主要關(guān)注的點(diǎn)是:
1)如何選取切點(diǎn)?
2)對切面的內(nèi)容,如何處理?
三、AOP權(quán)限處理
1、終于到正題了。這里方便理解,先上一張圖來說明:
2、代碼過程
1)權(quán)限申請
1、在清單文件中申明:略
2、
/**
* 發(fā)出權(quán)限申請請求
*/
@Permission(values = {Manifest.permission.ACCESS_FINE_LOCATION},requestCode = 200)
private void requestRequest200() {
Toast.makeText(this, "請求定位權(quán)限成功,200", Toast.LENGTH_SHORT).show();
}
@PermissionCancled(requestCode = 200)
private void cancelCode200(){
Toast.makeText(this, "取消__200", Toast.LENGTH_SHORT).show();
}
/**
* @return
* true 按照框架內(nèi)部定義的執(zhí)行,權(quán)限請求拒絕
* false 自定義權(quán)限請求拒絕內(nèi)容
*/
@PermissionDenied(requestCode = 200)
private boolean denyCode200(){
Toast.makeText(this, "禁止__200", Toast.LENGTH_SHORT).show();
return true;
}
2)AOP處理權(quán)限過程
此過程發(fā)生在項(xiàng)目編譯時(shí)進(jìn)行
@Aspect
public class PermissionAspect {
private static final String TAG = "PermissionAspect";
//定義切面的規(guī)則
//1.就在原來應(yīng)用中哪些注釋的地方放到當(dāng)前切面進(jìn)行處理
//execution(注釋名 注釋用的地方)
// 1、 execution( @com.dn.tim.lib_permission.annotation.Permission(切點(diǎn)函數(shù))
// *(類名,*表示任意的類都可以使用切點(diǎn)函數(shù)) *(方法名,*表示任意方法)(..(方法的參數(shù),..表示任意參數(shù))) )
// 2、@annotation(permission):傳入切點(diǎn)函數(shù)需要傳入的參數(shù)是注解類型的permission
@Pointcut("execution(@com.alin.commonlibrary.permission.annotation.Permission * * (..)) && @annotation(permission)")
public void requestPermission(Permission permission){
Log.i(TAG,"Pointcut===>");
}
//2.對進(jìn)入切面的內(nèi)容如何處理
//@Before() 在切入點(diǎn)之前運(yùn)行
//@After() 在切入點(diǎn)之后運(yùn)行
//@Around() 在切入點(diǎn)前后都運(yùn)行
@Around("requestPermission(permission)")
public void aroundJointPoint(final ProceedingJoinPoint joinPoint, final Permission permission){
final Object object = joinPoint.getThis();
Context context = null;
/**
* 兼容Fragment、Service、Activity處理
*/
if (object instanceof Context) {
context = (Context) object;
}else if (object instanceof Fragment){
context = ((Fragment)object).getActivity();
}else if (object instanceof android.app.Fragment){
context = ((android.app.Fragment)object).getActivity();
}
if (context == null || permission == null) {
Log.d(TAG, "aroundJonitPoint error ");
return;
}
int statusBarColor = PermissionUtil.findStatusBarColor(object);
final Context finalContext = context;
PermissionActivity.requestPermission(context, permission.requestCode(), permission.values(),statusBarColor ,new IPermission() {
@Override
public void granted() {
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void denied(String[] permissions, int code) {
}
@Override
public void canceled(String[] permissions, int code) {
}
});
}
3)PermissionActivity的權(quán)限處理過程
PermissionActivity要求:
1、PermissionActivity主題必須是完全透明的。
2、由于不同項(xiàng)目中,狀態(tài)欄statusBar的顏色設(shè)置都不一樣,為了不讓用戶發(fā)現(xiàn)這個(gè)假的PermissionActivity,需要提供設(shè)置狀態(tài)欄的方法。
3、PermissionActivity的進(jìn)入和退出動(dòng)畫都要被禁止掉。
4、啟動(dòng)模式必須是單例的。
public class PermissionActivity extends Activity{
public static final String PERMISSION_VALUE = "permission_value";
public static final String PERMISSION_CODE = "permission_code";
public static final String STATUS_BAR_COLOR = "status_bar_color";
private static IPermission sCallback;
public static void requestPermission(Context context, int code, String[] value,int statusBarColor,IPermission callback) {
sCallback = callback;
Intent intent = new Intent(context,PermissionActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
Bundle bundle = new Bundle();
bundle.putStringArray(PERMISSION_VALUE,value);
bundle.putInt(PERMISSION_CODE,code);
bundle.putInt(STATUS_BAR_COLOR,statusBarColor);
intent.putExtras(bundle);
context.startActivity(intent);
if (context instanceof Activity) {
((Activity)context).overridePendingTransition(0,0);
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permssion);
Intent intent = getIntent();
if (intent == null || intent.getExtras() == null || sCallback == null) {
finish();
return;
}
Bundle bundle = intent.getExtras();
String[] permissions = bundle.getStringArray(PERMISSION_VALUE);
int code = bundle.getInt(PERMISSION_CODE);
int statusBarColor = bundle.getInt(STATUS_BAR_COLOR,Integer.valueOf(PermissionUtil.DEFAULT_PERMISSION_COLOR));
// 設(shè)置狀態(tài)欄顏色
if (statusBarColor != Integer.valueOf(PermissionUtil.DEFAULT_PERMISSION_COLOR)) {
PermissionUtil.resetStatusBar(this,statusBarColor);
}
if (PermissionUtil.hasPermission(this,permissions)) {
sCallback.granted();
finish();
return;
}
ActivityCompat.requestPermissions(this,permissions,code);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//請求權(quán)限成功
if (PermissionUtil.verifyPermission(this,grantResults)){
sCallback.granted();
finish();
return;
}
//用戶點(diǎn)擊了不再顯示,拒絕授權(quán)
if (PermissionUtil.shouldShowRequestPermissionRationale(this,permissions)){
sCallback.denied(permissions,requestCode);
finish();
return;
}
//取消授權(quán)
sCallback.canceled(permissions,requestCode);
finish();
return;
}
@Override
public void finish() {
super.finish();
overridePendingTransition(0,0);
}
4)AOP處理回調(diào)過程
此過程都是先反射獲取權(quán)限申請端Activity的注解,最終反射調(diào)用被注解修飾的方法。
/**
* 成功
*/
@Override
public void granted() {
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
/**
* 拒絕
*/
@Override
public void denied(String[] permissions, int code) {
boolean dispatch = PermissionUtil.invokeAnnotation(object, PermissionDenied.class, code);
if (dispatch) {
PermissionUtil.goToAppMenu(finalContext);
}
}
/**
* 取消
*/
@Override
public void canceled(String[] permissions, int code) {
boolean dispatch = PermissionUtil.invokeAnnotation(object, PermissionCancled.class,code);
if (dispatch) {
PermissionUtil.goToAppMenu(finalContext);
}
}
再來看下工具類中的方法:
/**
* 其實(shí)就是反射調(diào)用被注解修飾的方法過程
* @return
* true 按照框架內(nèi)部定義的執(zhí)行,權(quán)限請求拒絕、取消
* false 自定義權(quán)限請求拒絕、取消內(nèi)容
*/
public static <T extends Annotation> boolean invokeAnnotation(Object target, Class<T> annotationClass, int code) {
Method[] methods = target.getClass().getDeclaredMethods();
if (methods == null || methods.length == 0 || annotationClass == null) {
return false;
}
for (Method method : methods) {
T annotation = method.getAnnotation(annotationClass);
boolean isFindCode = false;
if (annotation instanceof PermissionCancled) {
isFindCode = ((PermissionCancled)annotation).requestCode() == code;
Log.e(TAG,"caceled : code="+ code + ", realCode=" + ((PermissionCancled)annotation).requestCode() );
}else if (annotation instanceof PermissionDenied){
isFindCode = ((PermissionDenied)annotation).requestCode() == code;
Log.e(TAG,"denied , code="+ code + ", realCode=" + ((PermissionDenied)annotation).requestCode() );
}
boolean isHasAnnotation = method.isAnnotationPresent(annotationClass);
if (isHasAnnotation && isFindCode){
try {
method.setAccessible(true);
Object invoke = method.invoke(target);
LogUtil.i(TAG,"invoke====>" + invoke);
if (invoke != null && invoke instanceof Boolean) {
return (boolean) invoke;
}else if (invoke == null){
return true;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
return false;
}
5)補(bǔ)充說明
1、如何動(dòng)態(tài)的設(shè)置透明PermissionActivity的statusBar顏色?
由于AOP是編譯時(shí)自動(dòng)處理框架,所以,不能對外暴露方法給外面用。此時(shí),也只能通過注解的方式處理。
<1>在權(quán)限發(fā)起端,定義狀態(tài)欄色值
@PermissionColor(color = "#C81432")
public class TestPermissionActivity extends CommonActivity{}
<2>在PermissionAspect中反射獲取注解色值,然后傳給PermissionActivity進(jìn)行狀態(tài)欄statusBar改變
//獲取注解色值
int statusBarColor = PermissionUtil.findStatusBarColor(object);
//將色值穿給PermissionActivity,從而最終改變statusBar色值。
PermissionActivity.requestPermission(context, permission.requestCode(), permission.values(),statusBarColor ,new IPermission())
2、權(quán)限拒絕后的處理?
這里的靈感來自于Android事件處理機(jī)制,onTouchEvent(){ return true}。
如果權(quán)限發(fā)起端Activity中。被@PermissionDenied修飾的方法的返回值來處理的。
1、如果返回的是true,則使用的是框架內(nèi)部的方法。直接跳到控制面板
2、如果返回的是false,則不使用的是框架內(nèi)部的方法。用戶可以自定義
/**
* @return
* true 按照框架內(nèi)部定義的執(zhí)行,權(quán)限請求拒絕
* false 自定義權(quán)限請求拒絕內(nèi)容
*/
@PermissionDenied(requestCode = 200)
private boolean denyCode200(){
Toast.makeText(this, "禁止__200", Toast.LENGTH_SHORT).show();
return true;
}
/**
* 拒絕
*/
@Override
public void denied(String[] permissions, int code) {
//如果返回的是true,則使用的是框架內(nèi)部的方法。直接跳到控制面板
//如果返回的是false,則不使用的是框架內(nèi)部的方法。用戶可以自定義
boolean dispatch = PermissionUtil.invokeAnnotation(object, PermissionDenied.class, code);
if (dispatch) {
PermissionUtil.goToAppMenu(finalContext);
}
}
如果上述有不當(dāng)?shù)?,或者有?yōu)化的地方,歡迎指出。
項(xiàng)目地址