Android 切面编程

1、前言

做过JavaEE开发对切面编程AOP思想应该比较熟悉,而Android这方面使用的就比较少,所以这篇文章就带领大家入门Android AOP

2、介绍

可能我们比较熟悉的就是OOP思想了,同样AOP也是一种编程思想。在OOP中我们将功能统一成模块,一个功能一个模块。但是有时候难免会遇到这样的情况,有这么一个功能,其他的功能有需要用到这个功能。也就是说这个功能是横跨所有功能的,比如log功能,那么这个时候我们就需要使用AOP切面编程的思想来解决这个问题了。

所以AOP解决的问题是将这些分散的功能统一集中在一起,然后通过AOP在其他模块中使用。

下面我们通过打印log的例子来讲解

3、普通实例代码

在没有使用AOP的代码里,如果我们需要打印log,一般方法如下

public void login(View view){
        Log.i(TAG,"方法开始执行");

        System.out.println("调用登录方法");
        SystemClock.sleep(3000);

        Log.i(TAG,"方法执行完成") ;
    }

具体的log打印的方法需要放在方法体内部完成。乍一看这也没有什么不妥的地方,但是这样其实就打破了方法的功能了,而且很不美观。这里只是打印log的功能,要是其他功能比如权限验证等等,如果还是这样做的话岂不是很不好了。所以这就迫切的需要将这些功能集合起来,利用AOP来使用这些功能。

说了这么多,在Android中我们该如何来完成这些功能呢?

4、Aspectj介绍及使用

aspectj下载地址如下

AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

上面一段话是百度百科上的介绍,通过上面的话我们可以知道,aspectj有自己的编译器用来生成class文件,我们通常使用的都是javac,但是aspectj有自己的编译器,这就需要我们手动配置了。

配置aspectj分两种,一种是eclipse中配置和Android studio中配置,因为as使用的较多,所以这里只介绍Android studio的配置。

相对于eclipse,Android studio中配置aspectj就简单多了,我们只需要在app下的build.gradle中添加如下配置就可以了

import org.aspectj.bridge.IMessage
    import org.aspectj.bridge.MessageHandler
    import org.aspectj.tools.ajc.Main
    buildscript {
        repositories {
            mavenCentral()
        }
        dependencies {
            classpath 'org.aspectj:aspectjtools:1.8.9'
            classpath 'org.aspectj:aspectjweaver:1.8.9'
        }
    }

    apply plugin: 'com.android.application'

    repositories {
        mavenCentral()
    }

    android {
        compileSdkVersion 25
        buildToolsVersion "26.0.0"
        defaultConfig {
            applicationId "com.zhxu.aop"
            minSdkVersion 21
            targetSdkVersion 25
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }

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

    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.8",
                             "-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;
                }
            }
        }
    }



    dependencies {
        compile fileTree(include: ['*.jar'], dir: 'libs')
        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        compile 'com.android.support:appcompat-v7:23.0.0'
        testCompile 'junit:junit:4.12'
        compile files('libs/aspectjrt.jar')
    }

同时我们需要aspectjrt.jar,并将其引入到项目的依赖中。这样环境就配置完成了,下面我们就来通过代码实现AOP吧。

5、Aspectj代码实例

5.1、自定义注解

@Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TestTrace {
        String value() ;
    }

自定义注解如上,该自定义注解后面会使用,具体使用在后面讲解。

5.2、自定义Aspect

代码如下

@Aspect             //在TestAspect中这个注解必须添加
    public class TestAspect {

        /**
         * 切点
         */
        @Pointcut("execution(@com.zhxu.aop.TestTrace * *(..))")
        public void login(){}

        @Around("login()")
        public Object testObject(ProceedingJoinPoint point){

            MethodSignature methodSignature = (MethodSignature) point.getSignature();
            //获取方法上的注解
            TestTrace annotation = methodSignature.getMethod().getAnnotation(TestTrace.class);
            //获取注解中的value值
            String value = annotation.value();
            System.out.println("value:"+value);

            long startTime = System.currentTimeMillis();

            Object object = null ;
            try {
                //执行调用方法
                object = point.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }

            long endTime = System.currentTimeMillis();
            System.out.println("方法执行耗时:"+(endTime - startTime));

            return object ;
        }
    }

接下来定义我们使用的方法吧

public class MainActivity extends AppCompatActivity {

        public static final String TAG = "MainActivity" ;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }

        @TestTrace(value = "登录")
        public void login(View view){
            System.out.println("调用登录方法");
            SystemClock.sleep(3000);
        }
    }

在上述代码中,我们只是在login()方法上添加了@TestTrace的注解,执行后打印log如下:

09-11 00:31:17.435 1512-1512/com.zhxu.aop I/System.out: value:登录
09-11 00:31:17.435 1512-1512/com.zhxu.aop I/System.out: 调用登录方法
09-11 00:31:20.436 1512-1512/com.zhxu.aop I/System.out: 方法执行耗时:3000

我们发现了log中显示了方法执行耗时,这样我们就将打印log的模块抽取到了TestAspect中,并且通过注解调用了该模块。让方法做自己的事。

可能同学们看了这个后有点不是太明白

在自定义的Aspect中

@Pointcut(“execution(@com.zhxu.aop.TestTrace * *(..))”)

的含义,就是说将被TestTrace注解的任一类中的带有任意参数的任意方法作为切点,当然括号里写法有很多,可以参照下图

如果我们想将某一个类中的方法作为切点又不想使用注解的时候可以这样写

@Pointcut(“execution(* com.zhxu.aop.MianActivity.login(..))”)

6、原理

为什么我们通过这种方式就可以实现切面编程呢,aspectj的原理是什么呢?下面就通过反编译生成的MainActivity.class类来看看具体的原理。

在app的build路径下找到class文件

通过jd-gui打开MainActivity文件

结果显示如下

public class MainActivity extends AppCompatActivity
    {
      public static final String TAG = "MainActivity";
      private static final JoinPoint.StaticPart ajc$tjp_0;

      protected void onCreate(Bundle savedInstanceState)
      {
        super.onCreate(savedInstanceState);
        setContentView(2130968600);
      }

      @TestTrace(value="登录", type=1)
      public void login(View view)
      {
        View localView = view; JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_0, this, this, localView); Object[] arrayOfObject = new Object[3]; arrayOfObject[0] = this; arrayOfObject[1] = localView; arrayOfObject[2] = localJoinPoint; TestAspect.aspectOf().testObject(new MainActivity.AjcClosure1(arrayOfObject).linkClosureAndJoinPoint(69648));
      }

      static
      {
        ajc$preClinit();
      }

      static final void login_aroundBody0(MainActivity ajc$this, View view, JoinPoint paramJoinPoint)
      {
        System.out.println("调用登录方法");
        SystemClock.sleep(3000L);
      }

      private static void ajc$preClinit()
      {
        Factory localFactory = new Factory("MainActivity.java", MainActivity.class); ajc$tjp_0 = localFactory.makeSJP("method-execution", localFactory.makeMethodSig("1", "login", "com.zhxu.aop.MainActivity", "android.view.View", "view", "", "void"), 23);
      }
    }

可以看到和MainActivity.java有区别了,多了两个方法,并且login()方法内部也有了变化,这就是aspectj为我们做的事,这些都是在编译时完成的,所以使用aspectj注解不会产生性能问题。

编译完成的思路大体就是

  • 1、在编译时aspectj查找到标有@Aspect注解的类

  • 2、然后找到其中被@Pointcut注解标识的方法,然后找到里面的内容

  • 3、如果内容是注解就找到被注解标识的方法,如果是方法,就找到该方法然后进行处理

所以在程序运行时就和aspectj没有关系了,运行的是已经编译好的class文件了。

7、总结

这里文章中只是简单的介绍了aspectj的基本入门使用,如果想更深入的了解的话还需要自己动手去写一写。

QQ交流群

微信公众号:Android在路上,欢迎关注

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值