美文网首页
Android 进阶 AOP的应用 - AspectJ 的使用

Android 进阶 AOP的应用 - AspectJ 的使用

作者: 月影路西法 | 来源:发表于2019-10-09 20:07 被阅读0次

    在了解AOP直线需要先了解下注释,可以先看下我的另一篇文章
    android注解的使用

    AOP(Aspect Oriented Programming)是面向切面编程,AOP和我们平时接触的OOP编程是不同的编程思想,OOP是面向对象编程,提倡的是将功能模块化,对象化。而AOP的思想则是提倡针对同一类问题统一处理,当然,我们在实际编程过程中,不可能单纯的AOP或者OOP的思想来编程,很多时候,可能会混合多种编程思想。

    AOP的定义

    把某一方面的一些功能提取出来与一批对象进行隔离,提取之后我们就可以对某个单方面的功能进行编程。

    AOP的套路

    把众多方法中的所有共有代码全部抽取出来,放置到某个地方集中管理,然后在具体运行时,再由容器动态织入这些共有代码的话,最起码可以解决两个问题:
    1.1 Android程序员在编写具体的业务逻辑处理方法时,只需关心核心的业务逻辑处理,既提高了工作效率,又使代码变更简洁优雅。
    1.2 在日后的维护中由于业务逻辑代码与共有代码分开存放,而且共有代码是集中存放的,因此使维护工作变得简单轻松。
    1.3 面向切面编程AOP技术就是为解决这个问题而诞生的,切面就是横切面,代表的是一个普遍存在的共有功能,例如,日志切面、权限切面及事务切面等。

    AOP的作用

    一般来说,主要用于不想侵入原有代码的场景中,例如SDK需要无侵入的在宿主中插入一些代码,做日志埋点、性能监控、动态权限控制、甚至是代码调试等等。
    AOP的使用目前在android方面通常都是用eclipse的AspectJ
    另外一个比较成功的使用AOP的库是Jake大神的Hugo:
    Jake大神的Hugo

    [aspectj下载地址](http://www.eclipse.org/downloads/download.php?file=/tools/aspectj/aspectj-1.8.10.jar
    在网上还发现一个github的库,据说竭诚了aspectj
    https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx

    AspectJ 之 Join Points介绍

    Join Points在AspectJ中是关键的概念。Join Points可以看做是程序运行时的一个执行点,比如:一个函数的调用可以看做是个Join Points,相当于代码切入点。但在AspectJ中,只有下面几种执行点是认为是Join Points:

    Join Points 说明 实例
    method call 函数调用 比如调用Log.e(),这是一个个Join Point
    method execution 函数执行 比如Log.e()的执行内部,是一处Join Points。注意这里是函数内部
    constructor call 构造函数调用 和method call 类似
    constructor execution 构造函数执行 和method execution 类似
    field get 获取某个变量 比如读取DemoActivity.debug成员
    field set 设置某个变量 比如设置DemoActivity.debug成员
    pre-initialization Object在构造函数中做的一些工作。 -
    initialization Object在构造函数中做的工作。 -
    static initialization 类初始化 比如类的static{}
    handler 异常处理 比如try catch 中,对应catch内的执行
    advice execution 这个是AspectJ 的内容 -

    Pointcuts 介绍

    一个程序会有多个Join Points,即使同一个函数,也还分为call 和 execution 类型的Join Points,但并不是所有的Join Points 都是我们关心的,Pointcuts 就是提供一种使得开发者能够值选择所需的JoinPoints的方法。

    Advice介绍

    Advice就是我们插入的代码可以以何种方式插入,有Before 还有 After、Around。
    下面看个例子:

        /**
         * 找到处理的切点
         * * *(..)  可以处理所有的方法
         */
        @Pointcut("execution(@com.liuy.architect_day02.CheckNet * *(..))")
        public void checkNetBehavior() {
    
        }
    

    这里会分成好几个部分,我们依次来看:

    • @Before: Advice, 也就是具体的插入点
    • execution:处理Join Point的类型,例如call、execution
    • (* android.app.Activity.on(..)): 这个是最重要的表达式,第一个表示返回值,表示返回值为任意类型,后面这个就是典型的包名路径,其中可以包含 *来进行通配,几个 *没有区别。同时这里可以通过&&、||、!来进行条件组合。()代表这个方法的参数,你可以指定类型,例如android.os.Bundle,或者 (..) 这样来代表任意类型、任意个数的参数。
    • public void checkNetBehavior: 实际切入的代码。

    Before 和 After 其实还是很好理解的,也就是在Pointcuts之前和之后,插入代码,那么Android呢,从字面含义上来讲,也就是在方法前后各插入代码,他包含了 Before和 After 的全部功能,代码如下:

    @(“execution(* com.example.andorid.MainActivity.testAOP()))”)
    public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        String key = proceedingJoinPoint.getSignature().toString();
        Log.d(TAG,”onActivityMethodAroundFirst:”+key);
        proceedingJoinPoint.proceed();
        Log.d(TAG,”onActivityMethodAroundSecond:”+key);
    }
    

    以上代码中,proceedingJoinPoint.proceed()代表执行原始的方法,在这之前、之后,都可以进行各种逻辑处理。

    自定义Pointcuts

    自定义Pointcuts可以让我们更加精准的切入一个或多个指定的切入点。
    首先我们要定义一个注解类

    @Target(ElementType.METHOD) // Target 放在哪个位置
    @Retention(RetentionPolicy.RUNTIME)// RUNTIME 运行时 xUtils  CLASS 代表编译时期 ButterKnife   SOURCE 代表资源
    public @interface CheckNet { // @interface 注解
    
    }
    

    在需要插入代码的地方加入这个注解,例如在MainActivity中加入:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        @CheckNet
        public void click(View view) {
            Intent intent = new Intent(this, MainActivity.class);
            startActivity(intent);
        }
    
        @CheckNet
        public void postData() {
    
        }
    }
    

    最后创建切入代码,我这里的例子是判断网络是否可用的

    @Aspect
    public class SectionAspect {
    
        /**
         * 找到处理的切点
         * * *(..)  可以处理所有的方法
         */
        @Pointcut("execution(@com.darren.architect_day02.CheckNet * *(..))")
        public void checkNetBehavior() {
    
        }
    
        /**
         * 处理切面
         */
        @Around("checkNetBehavior()")
        public Object checkNet(ProceedingJoinPoint joinPoint) throws Throwable {
            Log.e("TAG", "checkNet");
            // 做埋点  日志上传  权限检测(我写的,RxPermission , easyPermission) 网络检测
            // 网络检测
            // 1.获取 CheckNet 注解  NDK  图片压缩  C++ 调用Java 方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            CheckNet checkNet = signature.getMethod().getAnnotation(CheckNet.class);
            if (checkNet != null) {
                // 2.判断有没有网络  怎么样获取 context?
                Object object = joinPoint.getThis();// View Activity Fragment ; getThis() 当前切点方法所在的类
                Context context = getContext(object);
                if (context != null) {
                    if (!isNetworkAvailable(context)) {
                        // 3.没有网络不要往下执行
                        Toast.makeText(context,"请检查您的网络",Toast.LENGTH_LONG).show();
                        return null;
                    }
                }
            }
            return joinPoint.proceed();
        }
    
        /**
         * 通过对象获取上下文
         *
         * @param object
         * @return
         */
        private Context getContext(Object object) {
            if (object instanceof Activity) {
                return (Activity) object;
            } else if (object instanceof Fragment) {
                Fragment fragment = (Fragment) object;
                return fragment.getActivity();
            } else if (object instanceof View) {
                View view = (View) object;
                return view.getContext();
            }
            return null;
        }
    
        /**
         * 检查当前网络是否可用
         *
         * @return
         */
        private static boolean isNetworkAvailable(Context context) {
            // 获取手机所有连接管理对象(包括对wi-fi,net等连接的管理)
            ConnectivityManager connectivityManager = (ConnectivityManager)
                    context.getSystemService(Context.CONNECTIVITY_SERVICE);
            if (connectivityManager != null) {
                // 获取NetworkInfo对象
                NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo();
    
                if (networkInfo != null && networkInfo.length > 0) {
                    for (int i = 0; i < networkInfo.length; i++) {
                        // 判断当前网络状态是否为连接状态
                        if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }
    }
    

    execution语法

    语法结构:execution([修饰符] 返回值类型 方法名(参数) [异常模式]) 实例
    execution(public .(..)) 所有的public方法
    execution(* hello(..)) 所有的hello()方法
    execution(String hello(..)) 所有返回值为String的hello方法。
    execution(* hello(String)) 所有参数为String类型的hello()
    execution(* hello(String..)) 至少有一个参数,且第一个参数类型为String的hello方法
    execution(* com.aspect..*(..)) 所有com.aspect包,以及子孙包下的所有方法
    execution(* com...Dao.find*(..)) com包下的所有一Dao结尾的类的一find开头的方法

    call和execution的区别

    call为调用,而execution为执行。

    3 public class HelloWorld {
    4   public static void main(int i){
    5       System.out.println("in the main method  i = " + i);
    6   }
    7   
    8   public static void main(String[] args) {
    9       main(5);
    10  }
    
    

    我们拦截了参数为:int的main方法。 这里用到了一内置的对象:thisJoinPoint,他表示当前jionPoint. 跟我们在java中的this 其实是差不多的,如果你不明白,那么你多运行一下,好好体会一下。getSourceLocation()表示源代码的位置:

    public aspect HelloAspect {
     
        pointcut HelloWorldPointCut() : call(* main(int));
        
        
        
       before() : HelloWorldPointCut(){
          System.out.println("Entering : " + thisJoinPoint.getSourceLocation());
        }
    }
    
    

    我们运行一下HelloWorld.java。
    打印结果为

    Entering : HelloWorld.java:9
    in the main method  i = 5
    

    9是行号
    接下来我们把call 换成execution打印

    public aspect HelloAspect {
     
        pointcut HelloWorldPointCut() : execution(* main(int));
        
        
        
       before() : HelloWorldPointCut(){
          System.out.println("Entering : " + thisJoinPoint.getSourceLocation());
        }
    }
    
    Entering : HelloWorld.java:4
    in the main method  i = 5
    

    结果为4

    从上面的结果可以看出,call是调用的地方,execution是执行的地方。
    thisJoinPoint.getSourceLocation() 这段代码将会在我们以后的Demo中经常用到。这是一个跟踪调试的好办法。

    within 和 withincode

    within

    还是上面的例子,如果别的类中也有main方法?应该怎么办?你首先想到的肯定是修改pointcut,指定到我们的HelloWorld类。 这当然是可以的,假设:现在还有5个类,也有main方法,也需要拦截。那你这解决办法肯定就不行了。(当然你也可以用 || 来组合他们)。这个时候就用到了我们的within了。代码如下

        pointcut HelloWorldPointCut() : execution(* main(..)) && !within(HelloAspectDemo);
        
       before() : HelloWorldPointCut(){
          System.out.println("Entering : " + thisJoinPoint.getSourceLocation());
    
    

    withincode

    withincode与within相似,不过withcode()接受的signature是方法,而不是类。用法,意思都差不多,只不过是使用场合不同。

    public class MainActivity extends AppCompatActivity {
        final String TAG = "MainActivity";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            register1();
            register2();
            register3();
        }
    
        public void registerTest() {
            Log.e(TAG, "execute registerTest");
        }
    
        public void register1(){
            registerTest();
        }
    
        public void register2(){
            registerTest();
        }
    
        public void register3(){
            registerTest();
        }
    }
    

    以上三个register方法都调用了registerTest()方法,如果这个时候想调用register3的registerTest()的方法,需要如下操作

    @Pointcut("(call(* *..registerTest()))&&withincode(* *..register2())")
    public void invokeregisterTestInregister2() {
    }
    
    @Before("invokeregisterTestInregister2()")
    public void beforeInvokeregisterTestInregister2(JoinPoint joinPoint) throws Throwable {
        Log.e(TAG, "method:" + getMethodName(joinPoint).getName());
    }
    
    private MethodSignature getMethodName(JoinPoint joinPoint) {
        if (joinPoint == null) return null;
        return (MethodSignature) joinPoint.getSignature();
    }
    

    输出如下

    execute registerTest
    method:registerTest
    execute registerTest
    execute registerTest
    

    相关文章

      网友评论

          本文标题:Android 进阶 AOP的应用 - AspectJ 的使用

          本文链接:https://www.haomeiwen.com/subject/wbpgpctx.html