Android AOP 面向切面编程 - AspectJ

本文介绍了Android中的AOP面向切面编程,重点讲解了AspectJ的使用,包括AOP概念、实现方式、术语解释、表达式与通配符,以及在Android中的实际应用,如在Activity生命周期中添加log打印和防止按钮重复点击。

AOP 概念

AOP 是 Aspect Oriented Programming 的缩写,意为 面向切面编程,通过预编译和运行期动态代理实现程序功能的统一维护的一种技术。利用 AOP 可以实现对代码的业务逻辑进行隔离,降低各功能间的耦合度。

使用场景: 需求是在类的每个方法中代码执行之前添加一句日志打印,在没有使用 AOP 的情况下,就需要在每个方法中手动添加日志打印,使用了 AOP ,就可以将打印日志代码在编译期间插入方法中,我们维护时二者的逻辑是分开的。

Android AOP 的实现方式

AOP 和 OOP 一样只是一种编程思想,它的实现方式主要有以下几种:

  • APT :AnnotationProcessor 在编译时生成 Java 文件。
  • AspectJ : 在将 .java 文件编译为 .class 文件时进行代码的注入。
  • Javassist : 对编译好的 class 字节码文件进行操作。
    在这里插入图片描述

AOP 术语

JoinPoint:连接点,程序执行过程中明确的点(被拦截的方法,字段,构造器)

Pointcut : 切点,用来描述 JoinPoint 连接点的表达式。比如描述调用 Animal.fly() 方法的地方,则写成

call(* Animal.fly(**))

Advice : 增强,表示在 Pointcut 里面定义的程序点具体要做的操作,通过 before,after,around 来区分是在 Jointpoint 之前,之后还是代替执行的代码。

Aspect :切面,类似 Java 中的类声明,Pointcut 和 Advice 合在一起称作 Aspect。

JoinPoint说明Pointcut 语法
method call函数被调用call(MethodSignature)
method execution函数执行内部execution(MethodSignature)
constructor call构造函数被调用call(MethodSignature)
constructor execution构造函数执行内部execution(ConstructorSignature)
field get读成员变量get(FieldSignature)
field set写成员变量set(FieldSignature)
static initializationstatic 块初始化staticinitialization(TypeSignature)
handler异常处理handler(TypeSignature)只能与 @Before()配合使用
advice executionadvice 执行adviceexecution
Advice说明
@Before(Pointcut)执行 JoinPoint 之前
@After(Pointcut)执行 JoinPoint 之后
@AfterReturning@AfterReturning(pointcut=-“xx”,returning=“returnValue”)
@AfterThrowing@AfterThrowing(pointcut=“xx”,throwing=“throwable”)
@Around(Pointcut)替代原来的代码,如果要执行原来的代码,需要使用 ProceedingJoinPoint.proceed()

AOP 表达式 & 通配符

execution 基本语法:

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)

除了返回类型模式,方法名模式,参数模式外,其他项都是可选的。

  • execution(public * *(…)) : 匹配目标类所有 public 方法, 第一个 * 代表返回类型,第二个 * 代表方法名,而 … 代表任意方法参数。
  • execution(* *To(…)) : 匹配目标类所有以 To 结尾的方法,第一个 * 代表任意返回类型,而 *To 代表任意以 To 结尾的方法。
  • execution(* com.xing.MainActivity.*(…)) : 匹配 MainActivity 中所有的方法,第一个 * 代表任意返回类型,第二个 * 代表方法名任意。
  • execution(*com.xing.demo.MainPresenter+.*(…)) : 匹配 MainPresenter 接口及其所有实现类的方法。
  • execution(* com.xing.*(…)) : 匹配 com.xing 包下所有类的所有方法。
  • execution(* com.xing…*(…)) : 匹配 com.xing 包,子孙包下所有类的所有方法。
  • execution(* com…*.*Dao.find*(…)) : 匹配 com 包,子孙包下以 Dao 结尾的类中所有以 find 为前缀的方法。

Android AspectJ 实现 AOP

Android 中使用 AspectJ 步骤:

(1) 项目根目录 build.gradle 配置 classpath

buildscript {
    repositories {
        google()
        jcenter()
        mavenCentral()   // AspectJ 需要 maven
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
        classpath 'org.aspectj:aspectjtools:1.8.9'
//        classpath 'org.aspectj:aspectjweaver:1.8.9'
    }
}

(2) app build.gradle 添加依赖和 task

apply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.xing.aspectjsample"
        minSdkVersion 15
        targetSdkVersion 28
        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 {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'org.aspectj:aspectjrt:1.8.9'
}

AspectJ 实战

实战1 - Activity 生命周期函数中添加 log 打印。
/**
 * 定义切面
 */
@Aspect
public class TraceAspect {
    /**
    * 定义切点,拦截 MainActivity 中所有以 on 为前缀的方法
    */
    @Pointcut("execution(* com.xing.aspectjsample.MainActivity.on*(..))")
    public void onLifecycleLog(){}

    /**
    * 在所有以 on 为前缀的方法执行前添加 log.e 打印
    */
    @Before("onLifecycleLog()")
    public void handleLifecycleLog(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        Log.e("MainActivity", name + "-------->>>>>" + joinPoint);
    }
}
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "TraceAspect";

    private int count = 12;

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

    private int getCount() {
        return count;
    }

    @SingleClick
    public void click(View view) {
        Log.e(TAG, "click: ");
        Toast.makeText(this, "the button is clicked", Toast.LENGTH_SHORT).show();
    }

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


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


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

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

    @Override
    protected void onRestart() {
        super.onRestart();
    }

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

在这里插入图片描述

实战2 - 防止按钮重复点击

防止按钮重复点击思路是:两次点击的时间间隔小于指定值,不产生点击作用。

定义注解 SingleClick

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SingleClick {
    long value() default 1000;
}

定义切面类,拦截类中标注了 SingleClick 注解,参数为 View 的方法进行增强处理。

@Aspect
public class SingleClickAspect {
    private static final String TAG = "SingleClickAspect";

    private long lastClickTime;

    /**
     * 定义切点,标记切点为所有被 @SingleClick 注解修饰的方法
     */
    @Pointcut("execution(@com.xing.aspectjsample.SingleClick * *(..))")
    void singleClickAnnotated() {

    }

    /**
     * 定义一个 Advice,包裹切点方法
     *
     * @param joinPoint
     */
    @Around("singleClickAnnotated()")
    public void handleSingleClick(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法参数
        View view = null;
        for (Object arg : joinPoint.getArgs()) {
            if (arg instanceof View) {
                view = (View) arg;
                break;
            }
        }
        if (view == null) {
            return;
        }
        // SingleClick 注解只修饰在方法上
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 只处理有 SingleClick 注解修饰的方法
        if (!method.isAnnotationPresent(SingleClick.class)) {
            return;
        }
        // 获取到 SingleClick 注解对象
        SingleClick singleClick = method.getAnnotation(SingleClick.class);
        // 获取注解值
        long value = singleClick.value();
        long currentTime = System.currentTimeMillis();
        if (currentTime - lastClickTime > value) {
            lastClickTime = currentTime;
            joinPoint.proceed();
        }
    }
}

Activity 中进行引用:

@SingleClick
public void click(View view) {
	Log.e(TAG, "click: ");
	Toast.makeText(this, "the button is clicked", Toast.LENGTH_SHORT).show();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值