Android之AOP打造权限申请框架

一、概念
AOP,既熟悉又陌生的名称。相信只要在IT相关的这个行业混的,或多或少都在一些地方看到或者听到过这个词汇。是Aspect Oriented Programming的缩写,翻译过来的意思就是面向切面编程。莫子呢?啥子意思哦?是的,与我们大众所熟悉的OOP感觉又有那么一丝相似的味道,因为都带P吗,而且很多时候我们就喜欢P多的。比如PPP,PPPP,哈哈,跑题了,其实他们所描述的都是一种编程思想。
好了,回归正题。先列出我们听到AOP的时候所浮现的一些问题。
1、AOP到底能帮我们干什么?
2、AOP到底该怎么用?
我们以我们平常开发经常涉及的权限申请为例,在一些稍大或者稍复杂的APP中,我们经常需要申请大量的权限来使我们的程序能够正常运转下去。比如电话、相机、定位、外部存储等等,而这些权限其实都是在特定的地方或者场景才会使用到,并不是一开始就需要用到这么多权限。
我们先回顾下一个权限的申请

  private void applyPermission(){
        //进行权限申请
        ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CAMERA},1001);
    }
    private void doSomething(){
        //我们想要执行的代码
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //申请后的回调
        if(grantResults != null && grantResults.length > 0){
            if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
                //申请通过
                doSomething();
            }else {
                if(!ActivityCompat.shouldShowRequestPermissionRationale(this,permissions[0])){
                    //被永久拒绝的逻辑
                }else {
                    //被拒绝的逻辑
                }
            }
        }
    }

是的,这就是一个权限的申请,在每个需要使用的地方都需要这样去写。这也就出现了我们经常在使用一些APP的时候会遇到在第一次使用的时候就会要求申请大量的权限,因为我们一个权限可能会在多个场景多个页面中都会用到,如果在每个地方使用的时候在去申请,那就需要在每个使用的地方都要进行权限的判断和申请代码的编写,导致需要编写的代码量加大且增加了出错的可能性,所以就出现了一开始申请所有权限的神操作,这也是痛点所在。而如果我们使用AOP的编程思想就可以在几乎不增加原地方代码的基础上来实现即时申请即时使用。当然,现在有大量的网络申请框架也可以解决我们的痛点,而AOP只是其中一种。
二、引入
上面我花了一大段文字来假定我们的场景以及痛点,而明确的说AOP的编程就是可以解决我们痛点的手段之一。而在Android中AOP我们会使用AspectJ来实现,在AndroidStudio有多种引入方式,这里介绍两种。
1、原生方式
a、在工程build.gradle的dependencies中引入

classpath 'org.aspectj:aspectjtools:1.9.1'
classpath 'org.aspectj:aspectjweaver:1.9.1'

b、在module的build.gradle的dependencies中引入

implementation 'org.aspectj:aspectjrt:1.9.1'

c、在module的build.gradle的最外层,与dependencies同级引入

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

final def log = project.logger
final def variants = project.android.libraryVariants

variants.all { variant ->
    if (!variant.buildType.isDebuggable()) {
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return
    }

    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.9",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
        log.debug "ajc args: " + Arrays.toString(args)

        MessageHandler handler = new MessageHandler(true)
        new Main().run(args, handler)
        for (IMessage message : handler.getMessages(null, true)) {
            switch (message.getKind()) {
                case IMessage.ABORT:
                case IMessage.ERROR:
                case IMessage.FAIL:
                    log.error message.message, message.thrown
                    break
                case IMessage.WARNING:
                    log.warn message.message, message.thrown
                    break
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break
            }
        }
    }
}

2、插件方式
a、在工程build.gradle的dependencies中引入

classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'

b、在module的build.gradle的dependencies中引入

implementation 'org.aspectj:aspectjrt:1.8.+'

c、在在module的build.gradle的apply plugin: 'com.android.application’下面添加


apply plugin: 'com.hujiang.android-aspectjx'

这样就将AspectJ的引入工作完成了。使用AspectJ有三大核心点
1、Pointcut(切入点)
告诉AspectJ你要在原有的业务逻辑的某一块植入自己的代码,植入的那个点就叫切入点
2、Advice(通知)
你要在某个切入点植入的具体的代码的业务逻辑块。典型的Advice类型有before、after、around,分别表示在目标的方法执行之前、执行之后和完全替代目标方法执行的代码。除了在方法中注入代码,也可能会对代码做其他修改,比如在一个class中增加字段或者接口。
3、Joint point(连接点)
程序中可能作为代码注入目标的特定点,例如一个方法调用或者方法入口
三、使用
有点拗啊,那么来点代码吧。
1、确定一个切入点。
可以是一个明确的方法比如onCreate,我们申请权限没法定义一定要使用什么方法,所以这里我们先定义一个注解

//要申请权限的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionApply {
    String[] value();
    int requestCode();
}
//申请失败的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionFailed {
    int requestCode();
}
//申请被永久拒绝的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionForever {
    int requestCode();
}

我们以PermissionApply这个注解作为切入点,就是被这个注解所标记的方法我们都会去切入,认为是需要进行权限申请的方法。
2、切入并通知
首先定义一个类,并使用@Aspect进行标记,表示这个类需要被Aspect编译。
定义我们切入的方法以及切入后的执行逻辑

@Pointcut("execution(@com.calm.permission.annotations.PermissionApply * * (..)) " +
            "&& @annotation(permissions)")
    public void pointMethod(PermissionApply permissions){}
    @Around("pointMethod(permissions)")
    public void permissionApply(final ProceedingJoinPoint joinPoint, PermissionApply permissions){
        Context context = null;
        //获取到切入的对象
        final Object aThis = joinPoint.getThis();
        if(aThis instanceof Context){
            context = (Context) aThis;
        }else if(aThis instanceof Fragment){
            context = ((Fragment)aThis).getContext();
        }
        //不符合需要切入的条件,执行原方法
        if(context == null || permissions == null ||
                permissions.value() == null || permissions.value().length == 0){
            try {
                joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            return;
        }
        //获取到注解中的数据 要申请的权限数组
        String[] values = permissions.value();
        //请求码
        final int requestCode = permissions.requestCode();
        PermissionActivity.launchActivity(context, values, requestCode, new PermissionCallback() {
            @Override
            public void permissionApplySuccess() {
                /**
                 * 申请权限成功,执行原程序逻辑
                 * 注意这里有个坑,因为用了try-catch 如果原程序里面的异常在这里被捕获了,
                 * 不会被Thread.UncaughtExceptionHandler这个类接管到了这是第一点
                 * 第二点是如果原程序的逻辑如果本来会明显出现崩溃情况的,由于这里try-catch的原因,
                 * 不会崩溃了,可能会对测试结果产生一些影响
                 */
                try {
                    joinPoint.proceed();
                } catch (Throwable throwable) {
                    throwable.printStackTrace();
                }
            }

            @Override
            public void permissionApplyFailed() {
                PermissionUtil.invokeMethod(aThis, PermissionFailed.class,requestCode);
            }

            @Override
            public void permissionApplyForever() {
                PermissionUtil.invokeMethod(aThis, PermissionForever.class,requestCode);
            }
        });
    }

以上代码用了点小技巧,在申请权限的时候弹出一个我们自己的透明Activity来接手权限的申请及接收回调逻辑,这样就不用在用户界面进行编码侵入。PermissionUtil是一个工具类

public class PermissionUtil {
    private PermissionUtil(){ }

    /**
     * 是否所有权限均已被申请
     * @param context
     * @param permissions
     * @return
     */
    public static boolean hasPermissionRequest(Context context,String[] permissions){
        for (String permission : permissions) {
            if(ContextCompat.checkSelfPermission(context,permission) != PackageManager.PERMISSION_GRANTED){
                return false;
            }
        }
        return true;
    }

    /**
     * 是否所有权限都申请成功了
     * @param result
     * @return
     */
    public static boolean hasPermissionSuccess(int... result){
        if(result == null || result.length == 0){
            return false;
        }
        for (int i : result) {
            if(i != PackageManager.PERMISSION_GRANTED){
                return false;
            }
        }
        return true;
    }

    /**
     * 是否存在被永久拒绝的权限
     * @param activity
     * @param permissions
     */
    public static boolean shouldShowRequestPermissionRationale(Activity activity, String[] permissions,int[] grantResults){
        for (int i = 0; i< grantResults.length; i++){
            if(grantResults[i] == PackageManager.PERMISSION_GRANTED){
                //i位的权限是申请通过的权限  shouldShowRequestPermissionRationale会返回false
                continue;
            }
            //被拒绝了的权限 看下是被永久拒绝
            if(!ActivityCompat.shouldShowRequestPermissionRationale(activity,permissions[i])){
                return true;
            }
        }
        return false;
    }

    /**
     * 将结果回调到被annotationClass这个注解的requestCode这个方法
     * @param o
     * @param annotationClass
     * @param requestCode
     */
    public static void invokeMethod(Object o,Class annotationClass,int requestCode){
        Class<?> aClass = o.getClass();
        //获取到所有的方法
        Method[] methods = aClass.getMethods();
        for (Method method : methods) {
            //方法是否被annotation所注解
            boolean annotationPresent = method.isAnnotationPresent(annotationClass);
            if(annotationPresent){
                Annotation annotation = method.getAnnotation(annotationClass);
                int code = -1;
                if(annotationClass == PermissionFailed.class){
                    code = ((PermissionFailed)annotation).requestCode();
                }else if(annotationClass == PermissionForever.class){
                    code = ((PermissionForever)annotation).requestCode();
                }
                if(code == requestCode){
                    try {
                        method.invoke(o);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
//一个透明的Activity,用来接手权限申请及回调
public class PermissionActivity extends AppCompatActivity {
    private static PermissionCallback permissionCallback;
    private static final String REQUEST_PERMISSIONS = "request.permissions";
    private static final String REQUEST_CODE = "request.code";
    private static final int REQUEST_CODE_DEFAULT = -1;
    public static void launchActivity(Context context,String[] permissions,
                                      int requestCode,PermissionCallback callback){
        permissionCallback = callback;
        Bundle bundle = new Bundle();
        bundle.putStringArray(REQUEST_PERMISSIONS,permissions);
        bundle.putInt(REQUEST_CODE,requestCode);
        Intent intent = new Intent();
        intent.putExtras(bundle);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        intent.setClass(context,PermissionActivity.class);
        context.startActivity(intent);
    }
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = getIntent();
        //参数错误,直接返回
        if(intent == null){
            finish();
            return;
        }
        String[] permissions = intent.getStringArrayExtra(REQUEST_PERMISSIONS);
        int requestCode = intent.getIntExtra(REQUEST_CODE,REQUEST_CODE_DEFAULT);
        if(permissions == null || permissions.length == 0 ||
            requestCode == REQUEST_CODE_DEFAULT || permissionCallback == null){
            finish();
            return;
        }
        //所有权限均已被申请,不需要在申请权限
        if(PermissionUtil.hasPermissionRequest(this,permissions)){
            permissionCallback.permissionApplySuccess();
            finish();
            return;
        }
        ActivityCompat.requestPermissions(this,permissions,requestCode);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //权限与回调长度不一致,有问题
        if(permissions.length != grantResults.length){
            permissionCallback.permissionApplyFailed();
            finish();
            return;
        }
        //所有权限都申请成功了
        if(PermissionUtil.hasPermissionSuccess(grantResults)){
            permissionCallback.permissionApplySuccess();
            finish();
            return;
        }
        /**
         * 有权限被永久拒绝了
         */
        if(PermissionUtil.shouldShowRequestPermissionRationale(this,permissions,grantResults)){
            permissionCallback.permissionApplyForever();
            finish();
            return;
        }
        //前面都不符合,表示被拒绝但非永久拒绝
        permissionCallback.permissionApplyFailed();
        finish();
    }

    @Override
    public void finish() {
        super.finish();
        permissionCallback = null;
        overridePendingTransition(0,0);
    }
}

以上就组成了我们基本无侵入的权限申请框架的初型,来看下我们的使用,是不是真的无侵入。

@PermissionApply(value = {Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.READ_PHONE_STATE,
            Manifest.permission.CAMERA},requestCode = 1001)
    public void permissionApply(View view) {
        Log.e("CALM","权限申请成功");
    }
    @PermissionFailed(requestCode = 1001)
    public void permissionFailed(){
        Log.e("CALM","权限申请失败");
    }
    @PermissionForever(requestCode = 1001)
    public void permissionForever(){
        Log.e("CALM","权限申请失败,被永久拒绝了");
    }

嗯嗯嗯,啥子呢,就这样用?莫豁我,跑不起来打死你。是的,没豁你,在你需要申请权限的方法上加permissionApply注解就可以申请权限了,而且我还贴心的将失败以及永久拒绝的回调也用注解帮你实现了,只要注解就可以了。但请注意,requestCode一定一定要对应,否则收不到回调哈。

OK,我们的权限申请框架就完成了,而且使用超简单。
国际惯例,代码地址:https://github.com/274948606/Progress/tree/master/permission

你可能感兴趣的:(学习笔记)