一种横向业务的解决方案 -- AOP

标签: AOP  APT  注解  动态权限

AOP(Aspect Oriented Programming)即面向切片编程。所谓面向切片编程,就是可以按照时间,将程序分成无数个时间节点,利用AOP的思想,可以在任何一个时间节点插入其他的代码,来实现自己的业务需求。换句话说,对于那些非用户需求,如:性能监控、日志记录、埋点统计、动态权限、安全控制、异常处理等,可以用AOP完美地实现。
以一个方法的执行为例子:

方法的执行顺序是A->B->C,通过AOP的方式,完全可以在B方法执行的前后插入其他的代码,以解决某种业务需求,例如方法执行时间的检测。
动态代理也是可以说是一种AOP思想的体现,只是它只能在接口方法执行的时候进行一些操作,而AOP可以在任何方法执行前,执行时和执行后做一些操作。

1. AOP涉及到的基本术语

横切关注点

AOP把软件分成两部分:核心关注点和横切关注点。核心关注点一般指的是一些涉及到用户业务的主线业务,而横切关注点就是指非用户业务(横向需求),但又需要穿插到主线业务中执行的代码,如日志记录等。

连接点

在核心关注点的地方可能存在横切关注点的地方,例如方法的入口、点击事件发生的地方等,这些就叫做连接点。

通知

在连接点处所执行的动作,也就是AOP织入的代码,就叫做通知,一般用三种:
before: 在目标方法执行之前的动作。
around: 替换目标代码的动作。
after: 在目标方法执行之后的动作。

切入点

连接点的集合,这些连接点可以确定什么时机会触发一个通知。也就是将代码切入到的目的类,目的方法就叫切入点。
切入点通常使用正则表达式或者通配符语法表示,可以指定执行某个方法,也可以制定执行多个方法,例如指定执行了标记某个注解的所有方法

切面

切入点和通知可以组合成一个切面,简单地说,切面就是切入到指定类指定方法的代码片段。

织入

将通知注入到连接点的过程。

其实开发人员主要关心的就是切入点和切面这两个东西。举个简单的例子,当程序写好了之后,忽然来了一了收集错误日志的功能,也就是需要在所有调用了Log.e(TAG,Msg)的地方将Msg和类信息保存到本地或则服务器。这时候总不可能挨个去改代码吧,利用AOP的思想就可以直接将“收集错误信息的代码段”做成一个切片,插入到Log类的e方法中,就像这样(使用Lancet实现AOP):

 @Proxy("e")
    @TargetClass("android.util.Log")
    public static int rocordLogInfo(String tag, String msg){

        RecordSDK.recordLog(This.get().getClass().getSimpleName(),
                msg);
        return (int) Origin.call();
    }

只需要添加极少的代码就可以解决这个需求了。一个字,爽!

2. AOP代码织入的时机

代码的织入过程一般有三个时机:
编译时织入:例如在Java类文件编译的时候插入完成特定业务需求的代码,需要通过特定的编译器插件来完成。
类加载时织入:通过自定义的类加载器ClassLoader的方式在目标咧的贝加载到虚拟机之前进行类的字节码的增强(插入字节码)。
运行时时织入:切面在运行中的某个时刻插入,例如动态代理。

3. AOP的应用 : 结合注解、APT实现Android动态权限的申请

众所周知,在Android 6.0以后,系统对权限的控制更加严格了,某系涉及到用户隐私的危险权限需要在用户使用的时候动态申请,对用户来说,这是一件好事儿,但是对开发者来说,这却是一个非用户需求(横向业务),在用户需求代码中融入这种横向业务无疑会增加代码的复杂性,所以,使用AOP的思想来实现这个需求对开发者来说是非常合适的,因为这并不会让代码变得复杂。
当然,基于不同的国产ROM,申请权限和对权限的处理可能有许多差异,这里只提供一种解决思路和基于官方处理权限的流程的一个库:PermissionManager,算是对这部分内容学习的一个总结。

3.1 官方的动态权限申请流程

一般会使用到v4包中的三个方法:

ContextCompat.checkSelfPermission()
ActivityCompat.requestPermissions()
ActivityCompat.shouldShowRequestPermissionRationale()

1.检查是否有某个权限:

if (ContextCompat.checkSelfPermission(context, Manifest.permission. SEND_SMS)
        != PackageManager.PERMISSION_GRANTED) {
    // 没有权限,需要申请。
}else{
    // 有权限
}

2.如果没有权限就去申请:

ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.SEND_SMS},10001 );

3.处理权限结果的回调。

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case 10001: {
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 用户同意授权
            } else {
                // 用户拒绝授权
            }
            return;
        }
    }
} 

4.如果用户点击了不再询问,shouldShowRequestPermissionRationale()会返回flase。只点击了拒绝授权,这个方法会返回true,所以也可以根据这个方法的返回值弹出一些说明,来提示用户为什么需要这个权限。

3.2 注解 + APT + AOP 优化申请权限的逻辑

1.注解 + APT的作用
根据注解,找到发起权限请求的类,并在编译时生成特定的回调Listener,用于将权限请求的结果回调到Activity或者Fragment中,如:

发起权限请求的是MainActivity

@NeedPermission
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        PermissionManager.requestPermissions(this,new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE});

    }
    @OnGranted
    void granted(){
        Log.d(TAG, "YES!用户同意了授权!");
    }
    @OnDenied
    void denied(){

        Log.d(TAG, "oh NO!用户拒绝了授权!");
    }

    @OnShowRationale
    void showRationale(){
        Log.d(TAG, "用户选择了了不再询问");
    }
}

那么则会在编译时生成如下代码:

public class MainActivity_PermissionListener implements PermissionListener<MainActivity> {
  @Override
  public void onGranted(MainActivity target) {
    target.granted();;
  }

  @Override
  public void onDenied(MainActivity target) {
    target.denied();;
  }

  @Override
  public void onShowRationale(MainActivity target) {
    target.showRationale();;
  }
}

2. AOP的使用时机

使用AOP可以将处理权限结果的相关代码织入onRequestPermissionsResult()方法中,这样就不必再重写这个方法了,所有对权限结果处理的逻辑都在使用注解(@OnGranted,@OnDenied, @OnShowRationale)标记的方法中处理了。

就比如像这样:

public class PremissionResultAspect {
    @TargetClass(value = "android.support.v7.app.AppCompatActivity", scope = Scope.LEAF)
    @Insert(value = "onRequestPermissionsResult", mayCreateSuper = true)
    public void onRequestPermissionsResultAspect(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        Log.d("xsz", "onRequestPermissionsResultAspect -> 权限的数量是 " + permissions.length);
        Origin.callVoid();
        PermissionListener listener = PermissionManager.getPermissionListener(This.get());
        if (grantResults.length > 0 && listener != null) {
            for (int i = 0; i < grantResults.length; i++) {
                if (PermissionHelper.hasGrantedPermission(grantResults[i])) {
                    //已经获得用户权限
                    listener.onGranted(this);
                } else {
                    // 用户拒绝授权
                    if (ActivityCompat.shouldShowRequestPermissionRationale((Activity) This.get(), permissions[i])) {
                        //用户拒绝了授权,但是没有点击不再询问
                        //提示用户为什么APP需要这个权限
                        listener.onDenied(this);
                    } else {
                        //返回false -> 用户点击了不再询问
                        listener.onShowRationale(this);
                    }
                }
            }
        }
    }

}

这就完成了对动态权限的请求的过程的简单封装,其主要目的还是学习这种AOP思想,代码细节以后再来慢慢完善。

版权声明:本文为xushuzhan原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xushuzhan/article/details/79319515