Android中AOP的实际运用

Android中AOP的实际运用

一、AOP简介

AOP即面向切面编程,区别于OOP(面向对象编程)的功能模块化,AOP主要侧重于解决某一类的问题。曾经我也不知道面向切面到底是切的谁,直到我看到下面这个图才理解。


AOP

从上图可以看出,面向切面编程是在不影响原业务功能的情况下,将我们所需要的功能插入进原业务代码中。

通俗的讲,比如在我们的android商城应用中,点击按钮跳转到功能界面时,很多地方都判断用户是否已登录,若用户已登录则跳转到功能界面,一般,我们的代码会这么写:

public void goToFun() {
    if(LoginUtil.isLogin()) {
        startActivity(new Intent(MainActivity.this, FunctonActivity.class));
    } else {
        startActivity(new Intent(MainActivity.this, LoginActivity.class));
    }
}

而在AOP思想中,代码会写成这样:

@CheckLogin
public void goToFun() {
    startActivity(new Intent(MainActivity.this, FunctonActivity.class));
}

乍一看,你可能会觉得代码并没有精简多少,但是若判断场景更加复杂、判断场景更多的时候,这样的写法很显然会非常冗余,并影响阅读体验。AOP的精髓就在于,不影响原代码业务逻辑的情况下,通过编译时注入代码的方式,通用的插入新的代码逻辑,效率高,侵入性低。

二、应用场景

AOP思想是用来解决一系列相同问题的方案,它可以运用的场景很多,比如:日志打印,性能监测,埋点方案等。

三、AOP实现方法

AOP是一种编程思想,实现它的框架有很多,其中最有名的是AspectJ,本文也是用AspectJ框架实现的。

1.AspectJ简介

拥有自己的编译器和语法,可以在编译期间将需要植入的代码编译成Java代码插入到你的源代码文件当中,它是一种几乎和Java完全一样的语言,而且完全兼容Java(AspectJ应该就是一种扩展Java,但它不是像Groovy那样的拓展)。

基础概念

在学习AspectJ之前,要先简单的了解一下几个概念,这个稍微了解一下就好了,不懂也没关系,看后面的示例就好了。

image
  • Advice(通知): 注入到class文件中的代码。典型的 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。 除了在方法中注入代码,也可能会对代码做其他修改,比如在一个class中增加字段或者接口。

  • Joint point(连接点): 程序中可能作为代码注入目标的特定的点,例如一个方法调用或者方法入口。

  • Pointcut(切入点): 告诉代码注入工具,在何处注入一段特定代码的表达式。例如,在哪些 joint points 应用一个特定的 Advice。切入点可以选择唯一一个,比如执行某一个方法,也可以有多个选择,比如,标记了一个定义成@DebguTrace 的自定义注解的所有方法。

  • Aspect(切面):Pointcut 和 Advice 的组合看做切面。例如,我们在应用中通过定义一个 pointcut 和给定恰当的advice,添加一个日志切面。

  • Weaving(织入): 注入代码(advices)到目标位置(joint points)的过程。

切入点表达式规则

切入点表达式帮助我们定位需要在哪些类和方法上面进行切面,可以指定某一个类,或者某个包下的某一些类,只要满足表达式规则的类,均会被切到。

表达式一般有两种写法,第一种表达式:

execution (public * com.sample.service.impl..*. *(..))

其中:

  • execution():表达式主体,必须要写,表达式的条件就从这个主体中判断;
  • public:第一个参数表示作用的方法可见级别,可以省略;
  • 第一个*:表示作用方法的返回值类型,*表示所有返回值类型;
  • com.sample.service.impl:表示作用的包的路径;
  • ..*:表示包下的所有子类及子孙类;
  • *(..):其中*表示方法名,(..)表示任意参数。

因此,第一种表达式表达的意思是:切入点为com.sample.service.impl包下的所有类及子类中的所有public方法。

第二种表达式:

execution(@com.zw.kotlindemo.aop.CheckCostTime * *(..))

相比第一种写法,多了一个@com.zw.kotlindemo.aop.CheckCostTime,后面的* *(..))和第一种是一样的,多出的部分表示的是切入点的方法必须有此注解标示才能匹配。

2.示例

下面,我们就通过AspectJ来实现一个AOP案例,主要实现功能为,监测Activity的每一个生命周期的调用时间。

2.1 导入依赖

在模块(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'
    }
}
repositories {
    mavenCentral()
}
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;
            }
        }
    }
}

并且,在模块的build.gradle文件中,添加依赖库:

implementation 'org.aspectj:aspectjrt:1.8.9'

2.2 新建注解,表示和我们这次功能相关

@Target(ElementType.METHOD) // 表示作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 表示在代码运行时也生效
public @interface CheckCostTime {
    String value() default "unknown";
}

2.3 新建一个Aspect类。

@Aspect
public class CheckCostTimeAspect {
    
    
    
}

2.4 在Aspect类中,声明切入点

@Pointcut("execution(@com.zw.kotlindemo.aop.CheckCostTime * *(..))")
public void executionCostTime() { }

表示从CheckCostTime这个接口切入,其中,* *表示可以为任意包名,(..)表示为任意参数。

2.5 在Aspect类中,声明通知

由于我们要监测方法的执行时间,所以必须在方法开始和方法结束都需要记录时间,因此,我们选用@Around注解。

@Around("executionCostTime()")
public Object checkCostTime(ProceedingJoinPoint joinPoint) throws Throwable {
    // 通过反射获取是否有CheckCostTime注解
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    CheckCostTime checkCostTime = methodSignature.getMethod().getAnnotation(CheckCostTime.class);
    if(checkCostTime != null) {
        // 被插入的方法执行前
        String funName = checkCostTime.value();
        long startTime = System.currentTimeMillis();
        // 被插入的方法执行时
        Object obj = joinPoint.proceed();
        // 被插入的方法执行后
        long endTime = System.currentTimeMillis();
        Log.e("ceshi","function " + funName + " cost: " + (endTime - startTime) + "ms");
        return obj;
    }
    return joinPoint.proceed();
}

Aspect类完整代码

package com.zw.kotlindemo.aop;

import android.util.Log;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

@Aspect
public class CheckCostTimeAspect {

    @Pointcut("execution(@com.zw.kotlindemo.aop.CheckCostTime * *(..))")
    public void executionCostTime() {

    }

    @Around("executionCostTime()")
    public Object checkCostTime(ProceedingJoinPoint joinPoint) throws Throwable {
        // 通过反射获取是否有CheckCostTime注解
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        CheckCostTime checkCostTime = methodSignature.getMethod().getAnnotation(CheckCostTime.class);
        if(checkCostTime != null) {
            // 被插入的方法执行前
            String funName = checkCostTime.value();
            long startTime = System.currentTimeMillis();
            // 被插入的方法执行时
            Object obj = joinPoint.proceed();
            // 被插入的方法执行后
            long endTime = System.currentTimeMillis();
            Log.e("ceshi","function " + funName + " cost: " + (endTime - startTime) + "ms");
            return obj;
        }
        return joinPoint.proceed();
    }

}

2.6 在Activity中运用

public class FourActivity extends AppCompatActivity {
    

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

    @CheckCostTime("onStart")
    @Override
    protected void onStart() {
        super.onStart();
    }

    @CheckCostTime("onResume")
    @Override
    protected void onResume() {
        super.onResume();
    }

    @CheckCostTime("onStop")
    @Override
    protected void onStop() {
        super.onStop();
    }

    @CheckCostTime("onPause")
    @Override
    protected void onPause() {
        super.onPause();
    }

    @CheckCostTime("onDestroy")
    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

}

2.7 打印结果

2019-07-05 15:55:40.271 16583-16583/com.zw.kotlindemo E/ceshi: function onCreate cost: 31ms
2019-07-05 15:55:40.275 16583-16583/com.zw.kotlindemo E/ceshi: function onStart cost: 0ms
2019-07-05 15:55:40.277 16583-16583/com.zw.kotlindemo E/ceshi: function onResume cost: 0ms
2019-07-05 15:55:44.743 16583-16583/com.zw.kotlindemo E/ceshi: function onPause cost: 0ms
2019-07-05 15:55:45.063 16583-16583/com.zw.kotlindemo E/ceshi: function onStop cost: 0ms
2019-07-05 15:55:45.065 16583-16583/com.zw.kotlindemo E/ceshi: function onDestroy cost: 1ms

总结

AOP的优点:

  • 侵入性低:在程序编译时注入代码,不影响原始业务代码;

  • 通用性强:专注解决一系列相同问题,减少代码冗余度;

  • 耦合度低:修改自身业务逻辑不影响原代码的执行。

AOP的缺点:

  • 性能问题:由于AOP的实现原理还是通过反射,对代码的执行效率可能会产生影响。

推荐阅读更多精彩内容

  • AOP实现可分为两类(按AOP框架修改源代码的时机): 静态AOP实现:AOP框架在编译阶段对程序进行修改,即实现...
    数独题阅读 1,419评论 0 22
  • Android 中的 AOP 编程 原文链接 : Aspect Oriented Programming in A...
    mao眼阅读 16,712评论 19 79
  • 概述 Spring是什么? Spring是一个开源框架,为了解决企业应用开发的复杂性而创建的,但是现在已经不止于企...
    fungi8阅读 595评论 2 8
  • 在上一篇使用自定义注解实现MVP中Model和View的注入中,使用了自定义的方式进行依赖注入这一篇我们将继续对注...
    奇葩AnJoiner阅读 363评论 0 2
  • What? As we all know,在进行项目构建时,追求各模块高内聚,模块间低耦合。然而现实并不总是如此美...
    MasterNeo阅读 1,084评论 0 17