Android AOP介绍及实现原理

深入理解Android之AOP

一、闲谈AOP

大家都知道OOP,即ObjectOriented Programming,面向对象编程。而本文要介绍的是AOP。AOP是Aspect Oriented Programming的缩写,中译文为面向切向编程。OOP和AOP是什么关系呢?首先:

l OOP和AOP都是方法论。我记得在刚学习C++的时候,最难学的并不是C++的语法,而是C++所代表的那种看问题的方法,即OOP。同样,今天在AOP中,我发现其难度并不在利用AOP干活,而是从AOP的角度来看待问题,设计解决方法。这就是为什么我特意强调AOP是一种方法论的原因!
l 在OOP的世界中,问题或者功能都被划分到一个一个的模块里边。每个模块专心干自己的事情,模块之间通过设计好的接口交互。从图示来看,OOP世界中,最常见的表示比如:

图中所示为AndroidFramework中的模块。OOP世界中,大家画的模块图基本上是这样的,每个功能都放在一个模块里。非常好理解,而且确实简化了我们所处理问题的难度。

OOP的精髓是把功能或问题模块化,每个模块处理自己的家务事。但在现实世界中,并不是所有问题都能完美得划分到模块中。举个最简单而又常见的例子:现在想为每个模块加上日志功能,要求模块运行时候能输出日志。在不知道AOP的情况下,一般的处理都是:先设计一个日志输出模块,这个模块提供日志输出API,比如Android中的Log类。然后,其他模块需要输出日志的时候调用Log类的几个函数,比如e(TAG,…),w(TAG,…),d(TAG,…),i(TAG,…)等。

在没有接触AOP之前,包括我在内,想到的解决方案就是上面这样的。但是,从OOP角度看,除了日志模块本身,其他模块的家务事绝大部分情况下应该都不会包含日志输出功能。什么意思?以ActivityManagerService为例,你能说它的家务事里包含日志输出吗?显然,ActivityManagerService的功能点中不包含输出日志这一项。但实际上,软件中的众多模块确实又需要打印日志。这个日志输出功能,从整体来看,都是一个面上的。而这个面的范围,就不局限在单个模块里了,而是横跨多个模块。

l 在没有AOP之前,各个模块要打印日志,就是自己处理。反正日志模块的那几个API都已经写好了,你在其他模块的任何地方,任何时候都可以调用。功能是得到了满足,但是好像没有Oriented的感觉了。是的,随意加日志输出功能,使得其他模块的代码和日志模块耦合非常紧密。而且,将来要是日志模块修改了API,则使用它们的地方都得改。这种搞法,一点也不酷。
AOP的目标就是解决上面提到的不cool的问题。在AOP中:

l 第一,我们要认识到OOP世界中,有些功能是横跨并嵌入众多模块里的,比如打印日志,比如统计某个模块中某些函数的执行时间等。这些功能在各个模块里分散得很厉害,可能到处都能见到。
l 第二,AOP的目标是把这些功能集中起来,放到一个统一的地方来控制和管理。如果说,OOP如果是把问题划分到单个模块的话,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。比如我们可以设计两个Aspects,一个是管理某个软件中所有模块的日志输出的功能,另外一个是管理该软件中一些特殊函数调用的权限检查。

二、没有AOP的例子

先来看没有AOP的情况下,代码怎么写。

public class AopDemoActivity extends Activity {  
   private static final String TAG = "AopDemoActivity";  
  onCreate,onStart,onRestart,onPause,onResume,onStop,onDestory返回前,都输出一行日志  
   protected void onCreate(Bundle savedInstanceState) {  
       super.onCreate(savedInstanceState);  
       setContentView(R.layout.layout_main);  
       Log.e(TAG,"onCreate");  
    }  
   protected void onStart() {  
       super.onStart();  
        Log.e(TAG, "onStart");  
    }  
   protected void onRestart() {  
       super.onRestart();  
        Log.e(TAG, "onRestart");  
    }  
    protectedvoid onResume() {  
       super.onResume();  
        Log.e(TAG, "onResume");  
   checkPhoneState会检查app是否申明了android.permission.READ_PHONE_STATE权限  
        checkPhoneState();  
    }  
   protected void onPause() {  
       super.onPause();  
        Log.e(TAG, "onPause");  
    }  
   protected void onStop() {  
       super.onStop();  
        Log.e(TAG, "onStop");  
    }  
   protected void onDestroy() {  
       super.onDestroy();  
        Log.e(TAG, "onDestroy");  
    }  
   private void checkPhoneState(){  
       if(checkPermission("android.permission.READ_PHONE_STATE")== false){  
           Log.e(TAG,"have no permission to read phone state");  
           return;  
        }  
       Log.e(TAG,"Read Phone State succeed");  
       return;  
    }  
   private boolean checkPermission(String permissionName){  
       try{  
           PackageManager pm = getPackageManager();  
          //调用PackageMangaer的checkPermission函数,检查自己是否申明使用某权限  
           int nret = pm.checkPermission(permissionName,getPackageName());  
           return nret == PackageManager.PERMISSION_GRANTED;  
        }......  
    }  
}  

代码很简单。但是从这个小例子中,你也会发现要是这个程序比较复杂的话,到处都加Log,或者在某些特殊函数加权限检查的代码,真的是一件挺繁琐的事情。

三、使用AspectJ在Android中实现Aop

AOP虽然是方法论,但就好像OOP中的Java一样,一些先行者也开发了一套语言来支持AOP。目前用得比较火的就是AspectJ了,它是一种几乎和Java完全一样的语言,而且完全兼容Java(AspectJ应该就是一种扩展Java,但它不是像Groovy[1]那样的拓展。)。当然,除了使用AspectJ特殊的语言外,AspectJ还支持原生的Java,只要加上对应的AspectJ注解就好。

用AspectJ的方法很简单,首先是定义一个DebugLog注解:

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 {
}

接下来就是使用AspectJ的方式,其设置PointCut:

@Aspect
public class Hugo {
    //带有DebugLog注解的所有类
    @Pointcut("within(@com.example.aoplib.DebugLog *)")
    public void withinAnnotatedClass() {}

    //在带有DebugLog注解的所有类,除去synthetic修饰的方法
    @Pointcut("execution(!synthetic * *(..)) && withinAnnotatedClass()")
    public void methodInsideAnnotatedType() {}

    //在带有DebugLog注解的所有类,除去synthetic修饰的构造方法
    @Pointcut("execution(!synthetic *.new(..)) && withinAnnotatedClass()")
    public void constructorInsideAnnotatedType() {}

    //在带有DebugLog注解的方法
    @Pointcut("execution(@com.example.aoplib.DebugLog * *(..)) || methodInsideAnnotatedType()")
    public void method() {}

   //在带有DebugLog注解的构造方法
    @Pointcut("execution(@com.example.aoplib.DebugLog *.new(..)) || constructorInsideAnnotatedType()")
    public void constructor() {}
    ....
}

上面代码的注释中,已经说明了每个PointCut的意义,也就是说,AspectJ会去寻找带有@DebugLog注解的类,或者带有@DebugLog注解的方法和构造方法。

接着是定义Advice:

@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;
    }
这些代码,和Hugo中的一模一样,我将其抽取,构成一个简单的library,叫做aoplib,看下图:

这里写图片描述

四、Android AOP实现原理全解析

我们本博客的重点是了解清楚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的代码,肯定也就无法实现我们的意图了。

猜你喜欢

转载自blog.csdn.net/Fly0078/article/details/80719863
今日推荐