Android进阶资深开发必备技能-切面编程

切面编程听起来可能有点陌生,不过现在越来越多的开发团队正在用上这种技术。 先说熟悉的面向对象编程 OOP,通常都是用各种对象/模块来负责具体的功能,互相之间尽量不耦合。 切面编程AOP(aspect-priented programming)是为了解决OOP中耦合无法解除的问题而产生的。

打个比方现在项目中有负责网络/数据存储/UI几个模块,每个模块都接入了另外一个Log模块。 虽然Log不属于前面三个的功能,但因为都接入了,所以他们在某种程度上就有了耦合,要修改Log模块的实现的时候会影响到其他三个模块的实现。

这篇文章用最简单的例子来描述AOP是怎么解决这种问题的。 (其实这是一篇AspectJ环境配置指南)

安装AspectJ

Android上的ApsectJ开发由几部分组成,AspectJ gradle插件,ApsectJ依赖,还有 AspectJ编译器。 首先安装AspectJ编译器很简单,就跟安装JAVA环境一样, 下载链接:http://www.eclipse.org/downloads/download.php?file=/tools/aspectj/aspectj-1.9.0.jar 目前最新的已经更新到1.9.1了。如果你电脑已经有JAVA环境的话直接运行这个jar包就行, 在安装完毕后需要配置环境变量到 aspectj的bin目录下,这里不赘述

export PATH="$PATH:~/Library/Android/sdk/platform-tools"
export PATH="$PATH:/usr/local/opt/gradle/gradle-4.1/bin"
export PATH="$PATH:~/Library/Android/sdk/ndk-bundle"
export PATH="$PATH:~/Library/flutter/bin"
export PATH="$PATH:~/Library/kotlinc/bin"
export PATH="$PATH:~/Library/AspectJ/bin" <- AspectJ的PATH

配置完后运行 ajc -v 应该可以看到对应输出

AspectJ Compiler 1.9.0 (1.9.0 - Built: Monday Apr 2, 2018 at 18:52:10 GMT)

配置Android Gradle增加AspectJ依赖

构建带AspectJ支持的Android App的流程是先按正常流程编译出 .class 文件后,再用 ajc 编译器在 .class文件中插入我们需要的代码。 首先需要把 AspectJ 依赖加到 gradle根目录中,

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.2'
        classpath 'org.aspectj:aspectjtools:1.8.9' //Aspect
        classpath 'org.aspectj:aspectjweaver:1.8.9' //Aspect
    }
}

然后在项目app目录的build.gradle需要添加以下内容,

apply plugin: 'com.android.application'
//+增加内容
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)]
        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler);
    }
}
//-增加内容

这段gradle脚本是在java编译完成后追加一个 acj 的编译流程, MessageHandler 是 AspectJ Tools中的对象,用来接收参数然后进行 acj 编译的。

最后再把 dependencies依赖加上对AspectJ的支持就可以了,

implementation 'org.aspectj:aspectjrt:1.9.0'

创建AspectJ代码

下面这部分代码看起来会一脸懵逼,不过目前先不用管具体的语法含义, 先跑起来环境,然后再结合理论慢慢在修改代码中感受就能快速的上手AOP了。 以一个HelloWorld为例子,我们的MainActivity中啥事情不干,只有基本的生命周期方法,

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

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

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

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

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

现在我们要写一个AspectJ类,这个类看起来会跟一般的Java类有点不同,可以理解为它只是用注解作为媒介,让ACJ编译器知道要去注入哪些方法。 这个类要做的事情是告诉ACJ编译器,要在MainActivity中的每个方法前面打印一行log,输出当前执行的是哪个方法,

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

    @Pointcut("execution(* phoenix.com.helloaspectj.MainActivity.**(..))")
    public void executeAspectJ() {
    }

    @Before("executeAspectJ()")
    public void beforeAspectJ(JoinPoint joinPoint) throws Throwable  {
        Log.d(TAG, "beforeAspectJ: injected -> " + joinPoint.toShortString());
    }
}

第一次接触AspectJ的看到这段代码有点摸不着头脑,解释一下几个注解的意思, @Aspect: 告诉ACJ编译器这是个AspectJ类 @PointCut: PointCut是AspectJ中的一个概念,跟它一起的另一个概念是 JoinPoint,这两个概念一起描述要注入的切面 @Before: 表示要注入的位置,常用的有 Before/After/Around,分别表示在执行前,执行后,和取代原方法

这里@PointCut注解后的参数表示的意思是对 MainActivity中的所有方法进行注入,参数用的是正则匹配语法。 下面看看这段代码执行的结果

07-26 16:04:56.611 22823-22823/? D/AspectTest: beforeAspectJ: injected -> execution(MainActivity.onCreate(..)) 07-26 16:04:56.661 22823-22823/? D/AspectTest: beforeAspectJ: injected -> execution(MainActivity.onStart())

看到虽然我们没有在MainActivity中写入log打印语句,但是通过AspectJ实现了,在MainActivity两个生命周期执行前插入了我们自己的log。

使用场景

AspectJ只是AOP的其中一种手段,类似的还有用 asm 去修改字节码。AOP之所以会有越来越多的人去了解,抽象上来说它可以非常好的去耦合。 高级点的可以用AOP来实现无痕埋点,数据收集,甚至修改SDK中动不了的代码。

原文发布于微信公众号 - Android每日一讲(gh_f053f29083b9)

原文发表时间:2018-07-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏张善友的专栏

TransactionScope和Enterprise Libray 3.0 Data Access Application Block

Enterprise Libray 3.0已经发布了,具体可参见TerryLee的 Enterprise Library 3.0 发布.下载了看看,有非常激动人...

2008
来自专栏Zephery

Spring AOP小记

一、概述 在通常的开发过程中,我们调用的顺序通常是controller->service-dao,其中,service中包含着太多的业务逻辑,并且还要不断调用d...

2924
来自专栏aoho求索

比较Spring AOP与AspectJ

本文翻译自Comparing Spring AOP and AspectJ(http://www.baeldung.com/spring-aop-vs-aspe...

3948
来自专栏Android群英传

基于XDanmuku的Android性能优化实战

1042
来自专栏java初学

spring框架(2)— 面相切面编程AOP

40511
来自专栏小巫技术博客

Android Crash之Java Crash分析

892
来自专栏我的小碗汤

这个坑你能填吗?

今天利用java发邮件,本地windows上测试时发送ok的,部署到服务器上却报异常,让我们走进异常,探索到底坑在哪里,并填之。

1002
来自专栏别先生

Spring之注解实现aop(面向切面编程)

1:Aop(aspect object programming)面向切面编程,名词解释:     1.1:功能:让关注点代码与业务逻辑代码分离     1.2:...

26810
来自专栏coder修行路

Go基础之--位操作中你所不知道的用法

之前一直忽略的就是所有语言中关于位操作,觉得用处并不多,可能用到也非常简单的用法,但是其实一直忽略的是它们的用处还是非常大的,下面先回顾一下位操作符的基础 位操...

2126
来自专栏生信技能树

把vcf文件转换为maf格式,肿瘤外显子上游分析教程到此为止

可能还有一些教程我漏掉了,毕竟这些年发布了近万篇教程了,大家直接我去我博客,生信菜鸟团就可以搜索,去我们的论坛,生信技能树里面也可以搜到。

4322

扫码关注云+社区

领取腾讯云代金券