Android AOP实现原理全解析

原创 2016年12月25日 15:15:42

     前天早晨在公交车上,知乎搜索了下Android的最新技术,回答还是很多的,我们搞技术的,永远不能落后,要随时与市场保持同步,这样才能跟上市场的步伐。有朋友提到了一个AOP的面向切面的编程技术,从这个名字上,大概就可以知道是干什么的,也有很多朋友举例就是在日志打印、权限检查等比较散的地方,使用AOP可以实现统一管理,还是非常方便的。百度一下,也有好多大神写的关于AOP的详细介绍:

     深入理解Android之AOP

     在AndroidStudio工具中开发的话,还需要编译build.gradle角本,详细情况可参考如下:

     Android 基于AOP监控之——AspectJ使用指南

     我们本博客的重点是了解清楚AOP的整个实现流程,是直接使用的别人的代码,代码也是在别人的博客中直接下载的,地址如下:

     使用AspectJ在Android中实现Aop

     Activity就一个,运行时就直接调用TestMain.TestAll()日志打印。我们先来看一下作者分module的用意。整个project分为aoplib、app、buildsrc、libinlib、testlib五个module,各module的意思应该也比较清楚,aoplib就是实现AOP功能的模块,app是本项目的启动模块,buildsrc是用来构建项目的模块,libinlib中作者只提供了一个TestLog类,而且只有一个方法,目的是用来测试AOP功能的,最后一个testlib是作者实现自己意图的模块,所有的测试类都是在这里的。我们要分析的重点就是aoplib和testlib这两个module了。

     我们从程序的运行过程来一步步分析,首先看一下MainActivity类的onCreate方法,请注意,作者为这个方法上加了一个@DebugLog注解,@DebugLog注解是自己实现的,实现代码如下,非常简单:

package com.example.aoplib;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.CLASS;

@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(CLASS)
public @interface DebugLog {
}
     从这个注释接口的定义上,我们可以看到,它的目标是使用在TYPE(接口、类、枚举、注解),METHOD(方法),CONSTRUCTOR(构造函数)三种类型上的,当前就是使用在MainActivity的onCreate方法上的。我们可以来看一下使用Aspect编译后的MainActivity的class文件:


     从这里大家可以非常清楚的看到,MainActivity的onCreate方法已经被替换了,它是按照Aspect编译规则生成的,我们可以再来看看其他带有@DebugLog注解的类或者方法对应生成的class文件,比如TestMethodClass类的class文件,整个文件代码如下:

public class TestMethodClass {
    public TestMethodClass() {
        (new Thread() {
            @DebugLog
            public void run() {
                JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this);
                Hugo var10000 = Hugo.aspectOf();
                Object[] var3 = new Object[]{this, var2};
                var10000.logAndExecute((new TestMethodClass$1$AjcClosure1(var3)).linkClosureAndJoinPoint(69648));
            }

            static {
                ajc$preClinit();
            }
        }).start();
    }

    @DebugLog
    public void spendTime1ms() {
        JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this);
        Hugo var10000 = Hugo.aspectOf();
        Object[] var3 = new Object[]{this, var2};
        var10000.logAndExecute((new TestMethodClass$AjcClosure1(var3)).linkClosureAndJoinPoint(69648));
    }

    @DebugLog
    public static void spendTime2ms() {
        JoinPoint var1 = Factory.makeJP(ajc$tjp_1, (Object)null, (Object)null);
        Hugo var10000 = Hugo.aspectOf();
        Object[] var2 = new Object[]{var1};
        var10000.logAndExecute((new TestMethodClass$AjcClosure3(var2)).linkClosureAndJoinPoint(65536));
    }

    @DebugLog
    public final void spendTime3ms() {
        JoinPoint var2 = Factory.makeJP(ajc$tjp_2, this, this);
        Hugo var10000 = Hugo.aspectOf();
        Object[] var3 = new Object[]{this, var2};
        var10000.logAndExecute((new TestMethodClass$AjcClosure5(var3)).linkClosureAndJoinPoint(69648));
    }

    static {
        ajc$preClinit();
    }
}
     在这个class文件中,我们可以看到,run()、spendTime1ms()、spendTime2ms()、spendTime3ms()几个方法全部都是这样样式,每个方法一共四句,第一句,构建一个切点JoinPoint,第二句调用Hugo.aspectOf()获取我们自己定义的Aspect处理类,第三句构造参数数组Object[],第四句调用当前类的相应方法。看到这里我们基本就明白AOP的原理了,它就是利用我们自己实现的一个注解,将所有的切点集中在一个地方处理的,这样,就可以把多个切点放在一起统一处理了,非常的方便!

     下面我们就来分析一个方法的执行过程,此项目中其他方法的实现是完全一样的,我们就以TestMethodClass类的spendTime1ms()为例来展开我们的分析,在编译完的class文件中,首先构造一个JoinPoint切点,Factory.makeJP()方法的实现就是使用传入的参数直接构造一个JoinPointImpl对象,第二句就是获取当前的Aspect处理类对象Hugo,此类必须要带有@Aspect注解,第三句就是构造方法执行的数组对象,第四句执行Aspect处理类的入口方法logAndExecute,Hugo类的完整代码如下:

@Aspect
public class Hugo {
    @Pointcut("within(@com.example.aoplib.DebugLog *)")
    public void withinAnnotatedClass() {}

    @Pointcut("execution(!synthetic * *(..)) && withinAnnotatedClass()")
    public void methodInsideAnnotatedType() {}

    @Pointcut("execution(!synthetic *.new(..)) && withinAnnotatedClass()")
    public void constructorInsideAnnotatedType() {}

    @Pointcut("execution(@com.example.aoplib.DebugLog * *(..)) || methodInsideAnnotatedType()")
    public void method() {}

    @Pointcut("execution(@com.example.aoplib.DebugLog *.new(..)) || constructorInsideAnnotatedType()")
    public void constructor() {}

    @Around("method() || constructor()")
    public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable {
        enterMethod(joinPoint);

        long startNanos = System.nanoTime();
        Object result = joinPoint.proceed();
        long stopNanos = System.nanoTime();
        long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);

        exitMethod(joinPoint, result, lengthMillis);

        return result;
    }



    private static void enterMethod(JoinPoint joinPoint) {

        CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();

        Class cls = codeSignature.getDeclaringType();
        String methodName = codeSignature.getName();
        String[] parameterNames = codeSignature.getParameterNames();
        Object[] parameterValues = joinPoint.getArgs();

        StringBuilder builder = new StringBuilder("\u21E2 ");
        builder.append(methodName).append('(');
        for (int i = 0; i < parameterValues.length; i++) {
            if (i > 0) {
                builder.append(", ");
            }
            builder.append(parameterNames[i]).append('=');
            builder.append(Strings.toString(parameterValues[i]));
        }
        builder.append(')');

        if (Looper.myLooper() != Looper.getMainLooper()) {
            builder.append(" [Thread:\"").append(Thread.currentThread().getName()).append("\"]");
        }

        Log.v(asTag(cls), builder.toString());

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            final String section = builder.toString().substring(2);
            Trace.beginSection(section);
        }
    }

    private static void exitMethod(JoinPoint joinPoint, Object result, long lengthMillis) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            Trace.endSection();
        }

        Signature signature = joinPoint.getSignature();

        Class cls = signature.getDeclaringType();
        String methodName = signature.getName();
        boolean hasReturnType = signature instanceof MethodSignature
                && ((MethodSignature) signature).getReturnType() != void.class;

        StringBuilder builder = new StringBuilder("\u21E0 ")
                .append(methodName)
                .append(" [")
                .append(lengthMillis)
                .append("ms]");

        if (hasReturnType) {
            builder.append(" = ");
            builder.append(Strings.toString(result));
        }

        Log.v(asTag(cls), builder.toString());
    }

    private static String asTag(Class cls) {
        if (cls.isAnonymousClass()) {
            return asTag(cls.getEnclosingClass());
        }
        return cls.getSimpleName();
    }
}
     可以看到,在logAndExecute方法的处理中就是打印了日志而已,当然,我们也可以根据我们自己需求实现不同的功能,比如权限检查,如果权限OK,就正常执行,如果不OK,则直接在这里抛出异常。

     好了,AOP的完整过程我们已经了解了,可以看出,整个执行过程还是比较简单的,就是使用了一个注解把我们的目标切点集中到一起进行处理,最后我们来总结一下,如果我们自己的项目要实现AOP的切面编程,应该要有几步:

     1:我们要使用Aspect,肯定就需要有相应的jar包了,jar包的资源非常多,大家可以在网上随便下载。

     2:要定义我们的切点,相当于本例中的DebugLog注解,它的@Target可以根据自己的需求来实现。

     3:定义自己的Aspect处理类,此类必须带有@Aspect注解,需要在它里面定义切点函数,定义切点的处理函数,也就是本例中Hugo类的logAndExecute方法了,Hugo类中的其他两个方法enterMethod、exitMethod只是为了实现日志打印的目的而写的,大家可以根据自己的需求具体实现。

     4:完成了上面两步,我们的项目中的Aspect框架就算搭建好了,下面就是添加切点了,也就是我们要在哪里切入的问题,可以通过添加我们自定义的注释来实现。

     5:我们的所有功能都完成了,最后还一定要注意编译的方式要使用Aspect编译器,如果大家使用javac编译器的话,那生成的class文件中根本没有Aspect的代码,肯定也就无法实现我们的意图了。

版权声明:本文为博主原创文章,未经博主允许不得转载。

深入理解Android之AOP

深入理解Android之AOP格式更加精美的PDF版请到:http://vdisk.weibo.com/s/z68f8l0xTgCLK 下载一、闲谈AOP大家都知道OOP,即ObjectOriente...
  • Innost
  • Innost
  • 2015年10月24日 19:48
  • 47657

使用AspectJ在Android中实现Aop

开题上一篇文章Android Aop预研中介绍了Aop的各种实现方式,并且在最后提到,选择AspectJ作为合适的开发方式。这篇文章通过我自己编写的一个例子,来说明AspectJ的使用。首先要声明,使...
  • kangaroo835127729
  • kangaroo835127729
  • 2016年07月24日 16:36
  • 10148

Android 开发中使用 AOP

大家对AOP应该都不陌生, 就算没有用过也肯定听说过. 用过或了解过Java AOP的同学应该都知道AspectJ的大名. 因为AspectJ与java程序完全兼容,几乎是无缝关联, 所以只需要做一些...
  • l2show
  • l2show
  • 2017年03月19日 14:19
  • 1665

Android进阶系列之AOP面向切面编程

Android的博大精深,不是一言两语能够说明道清的,它的魅力只有亲身去接触才能体会。就像美女一样 我先在这里申明一下,我这篇AOP只是学习笔记,适合入门级选手,如果阁下是大神想要参考AOP,那么...
  • sw5131899
  • sw5131899
  • 2016年12月26日 17:30
  • 4482

Android 基于AOP监控之——AspectJ使用指南

AspectJ的使用核心就是它的编译器,它就做了一件事,将AspectJ的代码在编译期插入目标程序当中,运行时跟在其它地方没什么两样,因此要使用它最关键的就是使用它的编译器去编译代码ajc。ajc会构...
  • woshimalingyi
  • woshimalingyi
  • 2016年05月27日 22:01
  • 8309

Android中的AOP

各位同学,目前已经转入至简书,欢迎来搞事情简书地址: http://www.jianshu.com/users/99f514ea81b3/timeline说一件开心的事情,我的这篇文章将被刊登在 鸿洋...
  • sinat_15877283
  • sinat_15877283
  • 2017年03月10日 11:35
  • 396

Android中的AOP编程之AspectJ实战实现数据埋点

文章背景 最近在给某某银行做项目的时,涉及到了数据埋点,性能监控等问题,那我们起先想到的有两种方案,方案之一就是借助第三方,比如友盟、Bugly等,由于项目是部署在银行的网络框架之内的,所以该方案不可...
  • XiNanHeiShao
  • XiNanHeiShao
  • 2017年07月01日 22:55
  • 2479

Android Aop预研

预研目的公司要求,希望我整理一下项目中的log日志,由于当前项目已经很庞大,包含多个自主开发的library,并且由多个开发人员共同完成。不同的同事,打log的方式都不一样,没有同一个的格式,因此我探...
  • kangaroo835127729
  • kangaroo835127729
  • 2016年07月24日 15:26
  • 3513

Android AOP 总结

AndroidAOP 总结 一、AOP 1.1 什么是AOP AOP,AspectOriented Programming 面向切面编程 OOP,Object-orientedprogramming面...
  • chenzhiqin20
  • chenzhiqin20
  • 2017年03月16日 21:24
  • 614

AOP在Android中的应用

官网地址:http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/ OOP (面向对象编程)针对业务...
  • huohacker
  • huohacker
  • 2017年02月23日 14:45
  • 324
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android AOP实现原理全解析
举报原因:
原因补充:

(最多只允许输入30个字)