国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

Android 采用AOP方式封裝6.0權(quán)限管理的方法

2019-10-22 18:13:19
字體:
供稿:網(wǎng)友

【一】背景

6.0運行時申請權(quán)限已經(jīng)是一個老生常談的內(nèi)容了,最近項目TargetSDKVersion升到23以上,所以我們也需要做權(quán)限管理,我想到的需求是這樣的:

1、支持單個權(quán)限、多個權(quán)限申請

2、運行時申請

3、無侵入式申請,無需關(guān)注權(quán)限申請的邏輯

4、除了Activity、Fragment之外,還需要支持Service中申請

5、對國產(chǎn)手機做兼容處理

第一、二點,Google都有對應(yīng)的API;

第三點可以通過自定義注解+AOP切面方式來解決。為什么采用AOP方式呢?首先看AOP定義: 面向切面編程(Aspect-Oriented Programming)。如果說,OOP(面向?qū)ο螅┤绻前褑栴}劃分到單個模塊的話,那么AOP就是把涉及到眾多模塊的某一類問題進行統(tǒng)一管理。 因為我們申請權(quán)限的邏輯都是基本一樣的,所以可以把申請權(quán)限的邏輯統(tǒng)一管理。

第四點稍微有點麻煩,因為Google提供的API只支持在Activity和Fragment中去申請權(quán)限,Service中并沒有相應(yīng)的API,比如項目中的某個Service里需要拿到當(dāng)前位置信息,并且不能確定定位權(quán)限已經(jīng)給了,所以在定位之前仍然需要判斷有沒有定位權(quán)限,按照常規(guī)邏輯好像是行不通了。腫么辦呢?先說一下我想到的辦法:通過一個透明的Activity去申請權(quán)限,并且把申請結(jié)果返回來,最后實踐也是這么做的,具體思路請往下看。

第五點也比較麻煩,如果都按Google標(biāo)準(zhǔn)來,那就不用考慮兼容問題了,但是國產(chǎn)安卓手機碎片化比較嚴(yán)重,且基本都修改了ROM,導(dǎo)致申請權(quán)限的API跟期望返回的結(jié)果不一致,這種的可能就需要特殊處理了。

調(diào)研了一下比較流行的三方庫,如PermissionsDispatcher 、RxPermissions ,做了一個簡單的總結(jié):

 

 

權(quán)限庫 是否使用注解 是否支持鏈?zhǔn)秸{(diào)用 是否支持Service 是否適配國產(chǎn)機
RxPermissions No Yes No No
PermissionsDispatcher Yes No No 適配了小米

 

RxPermissions是基于RX的思想,支持鏈?zhǔn)秸{(diào)用,簡單方便,但是他不支持Service調(diào)用;PermissionsDispatcher使用了編譯時解析注解的方式,通過apt自動生成.class方式幫我們?nèi)懮暾垯?quán)限的邏輯,很好很強大,并且適配了小米手機,但是它也不支持Service中去申請權(quán)限。考慮到我們項目中的應(yīng)用場景并且借鑒了PermissionsDispatcher的申請權(quán)限的邏輯,決定基于AOP方式手動擼一個權(quán)限管理庫出來。

【二】效果圖

先上一下最終的效果圖:

android,權(quán)限管理封裝,安卓6.0權(quán)限管理封裝,android6.0,權(quán)限管理

效果圖有點模糊,可以下載源碼運行一下看效果

【三】整體思路

首先,先定義一個說法,彈出系統(tǒng)權(quán)限彈窗,用戶沒有給權(quán)限,并且選中不再提示,這種情況稱為權(quán)限被拒絕;如果用戶沒有給權(quán)限,但是沒有選中不再提示,這種情況稱為權(quán)限被取消。申請權(quán)限、權(quán)限被取消、權(quán)限被拒絕都是采用注解的形式,分別為@NeedPermission、@PermissionCanceled、@PermissionDenied,注解都是聲明在Method級別上的。在我們的Activity、Fragment及Service中聲明注解,然后在AOP中解析我們的注解,并把申請的權(quán)限傳遞給一個透明的Activity去處理,并把處理結(jié)果返回來。這就是整體思路,可能會遇到的問題:

1、 不同型號的手機兼容問題(申請權(quán)限、跳設(shè)置界面)

2、AOP解析注解以及傳值問題

上面說了很多,其實用一個圖來表示更清晰一些:

android,權(quán)限管理封裝,安卓6.0權(quán)限管理封裝,android6.0,權(quán)限管理

UML時序圖.png

OK,通過上面的圖是不是更清晰了呢?其實最關(guān)鍵的地方就是AOP解析注解及傳值。AOP面向切面編程是一種編程思想,而AspectJ是對AOP編程思想的一個實踐,本文采用AspectJ來實現(xiàn)切面編程,簡單介紹AspectJ的幾個概念:

  1. JPoint:代碼可注入的點,比如一個方法的調(diào)用處或者方法內(nèi)部,對于本文來說即是注解作用的方法。
  2. Pointcut:用來描述 JPoint 注入點的一段表達式。見下面例子
  3. Advice:常見的有 Before、After、Around 等,表示代碼執(zhí)行前、執(zhí)行后、替換目標(biāo)代碼,也就是在 Pointcut 何處編織代碼。
  4. Aspect:切面,Pointcut 和 Advice 合在一起稱作 Aspect。

關(guān)于AspectJ的介紹及用法的文章很多,不了解的朋友可以去google下,直接列一下AOP切面代碼:

@Aspectpublic 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<String> 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();                }              }            }          }        });  }}

代碼有點多,但是思路還是挺清晰的,首先定義@Pointcut(描述的是我們的注解@NeedPermission),接著由Advice(@Around)及Pointcut構(gòu)成我們的切面Aspect, 在切面Aspect中,通過joinPoint.getThis()根據(jù)不同來源來獲得Context,接著跳轉(zhuǎn)到一個透明Activity申請權(quán)限并通過接口回調(diào)拿到權(quán)限申請結(jié)果,最后在不同的回調(diào)方法里通過反射把回調(diào)結(jié)果回傳給調(diào)用方。

【四】使用舉例

為了簡化AspectJ的各種配置,這里用了一個三方的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、在整個工程的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、如果你的項目里使用了混淆,需要在AOP代碼進行hook的類及方法名不能被混淆,即被注解作用的類及方法不能被混淆,需要在混淆配置里keep住, 比如:

package com.hujiang.test;public class A {  @NeedPermission  public boolean funcA(String args) {    ....  }}//如果你在AOP代碼里對A#funcA(String)進行hook, 那么在混淆配置文件里加上這樣的配置-keep class com.hujiang.test.A {*;}

4、終于配好了,都閃開,我要開始舉栗子了:

android,權(quán)限管理封裝,安卓6.0權(quán)限管理封裝,android6.0,權(quán)限管理

下面以Activity中申請權(quán)限為例,F(xiàn)ragment、Service中使用是一樣的,就不一一寫了,源碼中也有相應(yīng)使用的Demo

4.1 申請單個權(quán)限

申請單個權(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)限,是一個String[]數(shù)組;requestCode是請求碼,是為了區(qū)別開同一個Activity中有多個不同的權(quán)限請求,默認是0,如果同一個Activity中只有一個權(quán)限申請,requestCode可以忽略不寫。

/** * 權(quán)限被取消 * * @param bean CancelBean */@PermissionCanceledpublic void dealCancelPermission(CancelBean bean) {  Toast.makeText(this, "requestCode:" + bean.getRequestCode(), Toast.LENGTH_SHORT).show();}

聲明一個public方法接收權(quán)限被取消的回調(diào), 方法必須有一個CancelBean類型的參數(shù) ,這點類似于EventBus,CancelBean中有requestCode變量,即是我們請求權(quán)限時的請求碼。

/** * 權(quán)限被拒絕 * * @param bean DenyBean */@PermissionDeniedpublic void dealPermission(DenyBean bean) {    Toast.makeText(this,     "requestCode:" + bean.getRequestCode()+ ",Permissions: " + Arrays.toString(bean.getDenyList().toArray()), Toast.LENGTH_SHORT).show(); }

聲明一個public方法接收權(quán)限被取消的回調(diào), 方法必須有一個DenyBean類型的參數(shù) ,DenyBean中有一個requestCode變量,即是我們請求權(quán)限時的請求碼,另外還可以通過denyBean.getDenyList()來拿到被權(quán)限被拒絕的List。

4.2 申請多個權(quán)限

基本用法同上,區(qū)別是@NeedPermission后面聲明的權(quán)限是多個,如下:

/** * 申請多個權(quán)限 */@NeedPermission(value = {Manifest.permission.CALL_PHONE, Manifest.permission.CAMERA}, requestCode = 10)public void callPhone() {  Toast.makeText(this, "電話、相機權(quán)限申請通過", Toast.LENGTH_SHORT).show();}

value中聲明了兩個權(quán)限,一個電話權(quán)限,一個相機權(quán)限

4.3 跳轉(zhuǎn)到設(shè)置類

當(dāng)用戶拒絕權(quán)限并選中不再提示后,需要引導(dǎo)用戶去設(shè)置界面打開權(quán)限,因為國產(chǎn)手機各個設(shè)置界面不一樣,用通用的API可能會跳轉(zhuǎn)不到相應(yīng)的APP設(shè)置界面,這里采用了策略模式(下圖所示)

android,權(quán)限管理封裝,安卓6.0權(quán)限管理封裝,android6.0,權(quán)限管理

跳轉(zhuǎn)到設(shè)置類.png

如需做兼容,只需要在庫里修改,調(diào)用方是不需要處理的,調(diào)用方如需跳轉(zhuǎn)到設(shè)置界面,只需像下面這樣調(diào)用就OK了:

【五】總結(jié)

回看一下我們的需求,基本上都實現(xiàn)了:

1、首先通過@NeedPermission、@PermissionCanceled、@PermissionDenied三個注解來分別定義權(quán)限申請、被取消、被拒絕三種情況,如果不想處理被取消的邏輯就不用使用@PermissionCanceled注解,其他權(quán)限申請的邏輯調(diào)用方不用關(guān)心,是完全解耦的;

2、同時支持在Activity、Fragment、Service中去申請權(quán)限;

3、最后關(guān)于申請權(quán)限、跳設(shè)置界面的兼容問題,因為身邊的手機有限,不能測試出所有兼容問題,需要后續(xù)優(yōu)化。

關(guān)于在AOP中通過 反射方式 把權(quán)限申請結(jié)果返回給調(diào)用方,是參考了EventBus的方式,感覺這樣用起來更方便一些;之前的做法是在AOP對應(yīng)的Java類中聲明接口,調(diào)用方實現(xiàn)該接口,然后通過接口回調(diào)的方式將權(quán)限申請結(jié)果回傳,也能實現(xiàn)同樣效果,但是感覺沒有反射方式更方便。以上就是全部內(nèi)容,后面會貼出源碼,如有使用不當(dāng)之處,歡迎批評指正!

【六】源碼

源碼地址: https://github.com/crazyqiang/Aopermission

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持VEVB武林網(wǎng)。


注:相關(guān)教程知識閱讀請移步到Android開發(fā)頻道。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 长治市| 阿坝县| 乌拉特前旗| 南投市| 桦川县| 锦州市| 仁寿县| 连平县| 南丹县| 安吉县| 明溪县| 南康市| 二连浩特市| 安西县| 盈江县| 康定县| 鞍山市| 太保市| 蒙山县| 松阳县| 岗巴县| 萝北县| 巴中市| 乌拉特前旗| 乌兰察布市| 永州市| 盐亭县| 海晏县| 辽中县| 垦利县| 宾阳县| 焦作市| 潮安县| 龙泉市| 加查县| 宿州市| 铁岭市| 辉县市| 泸溪县| 油尖旺区| 平顺县|