美文网首页Android收藏集
Android AOP:最简单&粗暴(使用与原理)讲解

Android AOP:最简单&粗暴(使用与原理)讲解

作者: Linhaojian | 来源:发表于2018-12-13 10:20 被阅读346次

1.前言

  • 最近随着项目迭代业务与使用的用户逐渐增多,陆续的出现了一些性能或者BUG的问题(例如:1.某些功能执行响应慢;2.按键复电;等等)。
  • 根据上述部分性能问题,提出相对应的解决方案:
    • 某些功能执行响应慢 ---> 性能监控
    • 按键复点 ---> 按键防抖
  • 上述2个解决方案的最终实现原理:就是在执行函数的前或者后台添加对应的逻辑处理
  • 选择实现方式:
    1.直接在方法前后添加对应的逻辑。
    2.使用静态/动态代理。
    3.使用AOP。
  • 1与2方式:当涉及修改的方法或者类较多时,实现比较复杂,而且侵入性大,更不方便后期扩展与维护。
  • 第3方式:侵入性很少;实现简单;解耦性很大;易维护。而这种方式是如何使用?原理是什么?。
  • 今天就为大家揭开AOP的面纱。
  • 文章中实例 linhaojian的Github

2.介绍

2.1 AOP 是什么

  • AOP为Aspect Oriented Programming的缩写。意为:面向切面编程

2.2 AOP 作用

  • 通过预编译方式运行期动态代理,来改变原来执行结果的技术。

2.3 AOP 特点

  • 使原执行逻辑与改变执行逻辑解耦
  • 对原代码侵入性少
  • 容易扩展辅助功能(例如:日志)。

2.4 Android 中应用

  • 日志
  • 持久化
  • 性能监控
  • 数据校验
  • 缓存
  • 按钮防抖
  • 其他更多

2.5 AOP 术语

  • 通知、增强处理(Advice):就是你想要的功能,也就是上面说的日志、耗时计算等。
  • 连接点(JoinPoint):允许你通知(Advice)的地方,那可就真多了,基本每个方法的前、后(两者都有也行),或抛出异常是时都可以是连接点(spring只支持方法连接点)。AspectJ还可以让你在构造器或属性注入时都行,不过一般情况下不会这么做,只要记住,和方法有关的前前后后都是连接点。
  • 切入点(Pointcut):上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有十几个连接点了对吧,但是你并不想在所有方法附件都使用通知(使用叫织入,下面再说),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
  • 切面(Aspect):切面是通知和切入点的结合。现在发现了吧,没连接点什么事,连接点就是为了让你好理解切点搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过before,after,around等AOP注解就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
  • 织入(weaving): 把切面应用到目标对象来创建新的代理对象的过程。

2.6 AOP 注解

  • @Aspect:声明切面,标记类
  • @Pointcut(切点表达式):定义切点,标记方法
  • @Before(切点表达式):前置通知,切点之前执行
  • @Around(切点表达式):环绕通知,切点前后执行
  • @After(切点表达式):后置通知,切点之后执行
  • @AfterReturning(切点表达式):返回通知,切点方法返回结果之后执行
  • @AfterThrowing(切点表达式):异常通知,切点抛出异常时执行

@Pointcut、@Before、@Around、@After、@AfterReturning、@AfterThrowing需要在切面类中使用,即在使用@Aspect的类中。

2.7 切点表达式

AspectJ语法1.png
AspectJ语法2.png
AspectJ语法3.png
  • 示例:
    // 返回类型:*  类名+函数名:com.lhj.test_apt..*ck(..)   
    //  com.lhj.test_apt..*ck(..)  :意思是com.lhj.test_apt包下所有类中以ck结尾的函数
    @Pointcut("execution(*  com.lhj.test_apt..*ck(..))")

3.使用

  • 3.1 项目根目录(build.gradle)的dependencies中,添加如下代码:
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.8.13'    // add
        classpath 'org.aspectj:aspectjrt:1.8.13'       // add
    }
  • 3.2 在主项目或者需要引用的library的build.gradle中,示例如下:
apply plugin: 'com.android.library'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
android {
    compileSdkVersion 28
    defaultConfig {
        minSdkVersion 17
        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'
        }
    }
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // aop
    implementation 'org.aspectj:aspectjrt:1.8.13'
}
project.android.libraryVariants.all { variant ->
    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)]
        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler)
        def log = project.logger
        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:
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break;
            }
        }
    }
}
  • 3.3 定义切面类(例如:在源码一些方法上添加额外日志打印):
/**
 * 添加切面注解
 */
@Aspect
public class AopTest {
    private static final String TAG = "linhaojian";
    /**
     * 定义切入点(定义那些类或者方法需要改变)
     */
    @Pointcut("execution(*  com.lhj.test_apt..*ck(..))")
    public void pointcut1() {
    }
    /**
     * 使用注解方式,定义注解
     */
    @Pointcut("execution(@com.lhj.test_apt.DebugLog * *ck(..))")
    public void pointcut() {
    }
    /**
     * 前置通知,切点之前执行
     * @param point
     */
    @Before("pointcut()")
    public void logBefore(JoinPoint point){
        Log.e(TAG,"logBefore");
    }
    /**
     * 环绕通知,切点前后执行
     * @param joinPoint
     * @throws Throwable
     */
    @Around("pointcut()")
    public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Log.e(TAG,"logAround");
        // 1.执行切点函数(如果不调用该方法,切点函数不被执行)
        joinPoint.proceed();
    }
    /**
     * 后置通知,切点之后执行
     * @throws Throwable
     */
    @After("pointcut()")
    public void logAfter(JoinPoint point){
        Log.e(TAG,"logAfter");
    }
    /**
     * 返回通知,切点方法返回结果之后执行
     * @throws Throwable
     */
    @AfterReturning("pointcut()")
    public void logAfterReturning(JoinPoint point, Object returnValue){
        Log.e(TAG,"logAfterReturning ");
    }
    /**
     * 异常通知,切点抛出异常时执行
     * @throws Throwable
     */
    @AfterThrowing(value = "pointcut()",throwing = "ex")
    public void logAfterThrowing(Throwable ex){
        Log.e(TAG,"logAfterThrowing : "+ex.getMessage());
    }
}
  • 注意:注释1中,如果使用了@Around注释,记得调用joinPoint.proceed(),不然切点的函数是不会被调用的。
  • 3.4 切点的使用方式:
  • 无侵入式@Pointcut定义不包含注解时,就能实现源代码零修改的改变。
  • 低侵入式@Pointcut定义包含注解时,就需要在源代码的函数中添加相应的注解,如下代码块。
public class AopActivity extends AppCompatActivity {
    private Button button;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.aop);
        button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                click();
            }
        });
    }
    @DebugLog
    public void click(){
        Log.e("linhaojian","onClick()");
    }
}
  • DebugLog注解代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DebugLog {
}
  • 3.5 运行结果


    执行结果.png

4.原理

  • 在分析其原理前,我们先看看包含切点功能的类,编译之后的class文件内容:
public class AopActivity extends AppCompatActivity {
    private Button button;
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(layout.aop);
        this.button = (Button)this.findViewById(id.button);
        this.button.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                AopActivity.this.click();
            }
        });
    }
    @DebugLog
    public void click() {
        // 1
        JoinPoint var1 = Factory.makeJP(ajc$tjp_0, this, this);
        try {
            try {
                // 2
                AopTest.aspectOf().logBefore(var1);
               // 3
                click_aroundBody1$advice(this, var1, AopTest.aspectOf(), (ProceedingJoinPoint)var1);
            } catch (Throwable var4) {
                AopTest.aspectOf().logAfter(var1);
                throw var4;
            }
            AopTest.aspectOf().logAfter(var1);
            AopTest.aspectOf().logAfterReturning(var1, (Object)null);
        } catch (Throwable var5) {
            AopTest.aspectOf().logAfterThrowing(var5);
            throw var5;
        }
    }
}
@Aspect
public class AopTest {
    //...
    public static AopTest aspectOf() {
        if (ajc$perSingletonInstance == null) {
            throw new NoAspectBoundException("com.lhj.test_apt.AopTest", ajc$initFailureCause);
        } else {
            return ajc$perSingletonInstance;
        }
    }
    public static boolean hasAspect() {
        return ajc$perSingletonInstance != null;
    }
    static {
        try {
            // 4
            ajc$postClinit();
        } catch (Throwable var1) {
            ajc$initFailureCause = var1;
        }
    }
}
  • 从上面编译后的class文件,可以看很清楚编译时改变了原方法的执行流程或结果,添加了切面类中定义的一些额外执行逻辑(例如:logBefore、logAfter);
  • 注释1:通过工厂类,创建ProceedingJoinPoint的实例;
  • 注释2:AopTest.aspectOf()代表获取AopTest的单例(单例时在注释4中通过static块初始化);
  • 注释3:其实就是将原方法的执行内容封装至ProceedingJoinPoint,使它们之间关联。

5.总结

  • 到此,AOP就结束完毕。
  • 如果喜欢我的分享,可以点击 关注 或者 ,你们支持是我分享的最大动力 。
  • linhaojian的Github

欢迎关注linhaojian_CSDN博客或者linhaojian_简书

不定期分享关于安卓开发的干货。


写技术文章初心

  • 技术知识积累
  • 技术知识巩固
  • 技术知识分享
  • 技术知识交流

相关文章

网友评论

    本文标题:Android AOP:最简单&粗暴(使用与原理)讲解

    本文链接:https://www.haomeiwen.com/subject/gworhqtx.html