權(quán)限終極封裝——AOP之權(quán)限處理

我們都知道,在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)目地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,272評(píng)論 25 708
  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 14,075評(píng)論 2 59
  • 有時(shí)候會(huì)發(fā)現(xiàn)自己潛在的、所厭惡的人性。每周一公司都需要開晨會(huì),半個(gè)小時(shí),非常難熬,從最初的新鮮感到現(xiàn)在的抱怨,這半...
    倉小北閱讀 246評(píng)論 0 0
  • 沙漠里落難的女人呵!你懷里的寶石不是上帝也不是他卑微的天使仆人——使你孕育的,是上帝獨(dú)寵的兒子人類年輕的父親是江河...
    郭綠獅閱讀 536評(píng)論 0 2
  • 寒月古井上 波光印殘陽 南湖村中落 碧潭繞白墻 深巷引客去 緩步石板上 暮入宏村里 身居畫卷中
    為何而活閱讀 252評(píng)論 0 2

友情鏈接更多精彩內(nèi)容