Android切面编程入门讲解
切面编程听起来可能有点陌生,不过现在越来越多的开发团队正在使用上这种技术。
先说熟悉的面向对象编程OOP,通常都是用各种对象/模块来负责具体的功能,互相之间尽量不耦合。
切面编程AOP(aspect-prientedprogramming)是为了解决OOP中耦合无法解除的问题而产生的。
打个比方现在项目中有负责网络/数据存储/UI几个模块,每个模块都接入了另外一个Log模块。
虽然Log不属于前面三个的功能,但因为都接入了,所以他们在某种程度上就有了耦合,要修改Log模块的实现的时候会影响到其他三个模块的实现。
这篇文章用最简单的例子来描述AOP是怎么解决这种问题的。(其实这是一篇AspectJ入门指南)
安装AspectJ
Android上的ApsectJ开发由几部分组成,AspectJgradle插件,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目录下,这里不赘述
exportPATH="$PATH:~/Library/Android/sdk/platform-tools" exportPATH="$PATH:/usr/local/opt/gradle/gradle-4.1/bin" exportPATH="$PATH:~/Library/Android/sdk/ndk-bundle" exportPATH="$PATH:~/Library/flutter/bin" exportPATH="$PATH:~/Library/kotlinc/bin" exportPATH="$PATH:~/Library/AspectJ/bin"<-AspectJ的PATH
配置完后运行ajc-v应该可以看到对应输出
AspectJCompiler1.9.0(1.9.0-Built:MondayApr2,2018at18:52:10GMT)
配置AndroidGradle增加AspectJ依赖
构建带AspectJ支持的AndroidApp的流程是先按正常流程编译出.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需要添加以下内容,
applyplugin:'com.android.application' //+增加内容 importorg.aspectj.bridge.MessageHandler importorg.aspectj.tools.ajc.Main buildscript{ repositories{ mavenCentral() } dependencies{ classpath'org.aspectj:aspectjtools:1.8.9' classpath'org.aspectj:aspectjweaver:1.8.9' } } repositories{ mavenCentral() } finaldeflog=project.logger finaldefvariants=project.android.applicationVariants variants.all{variant-> if(!variant.buildType.isDebuggable()){ log.debug("Skippingnon-debuggablebuildtype'${variant.buildType.name}'.") return; } JavaCompilejavaCompile=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)] MessageHandlerhandler=newMessageHandler(true); newMain().run(args,handler); } } //-增加内容
这段gradle脚本是在java编译完成后追加一个acj的编译流程,
MessageHandler是AspectJTools中的对象,用来接收参数然后进行acj编译的。
最后再把dependencies依赖加上对AspectJ的支持就可以了,
implementation'org.aspectj:aspectjrt:1.9.0'
创建AspectJ代码
下面这部分代码看起来会一脸懵逼,不过目前先不用管具体的语法含义,
先跑起来环境,然后再结合理论慢慢在修改代码中感受就能快速的上手AOP了。
以一个HelloWorld为例子,我们的MainActivity中啥事情不干,只有基本的生命周期方法,
publicclassMainActivityextendsAppCompatActivity{ @Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protectedvoidonStart(){ super.onStart(); } @Override protectedvoidonPause(){ super.onPause(); } @Override protectedvoidonStop(){ super.onStop(); } @Override protectedvoidonDestroy(){ super.onDestroy(); } }
现在我们要写一个AspectJ类,这个类看起来会跟一般的Java类有点不同,可以理解为它只是用注解作为媒介,让ACJ编译器知道要去注入哪些方法。
这个类要做的事情是告诉ACJ编译器,要在MainActivity中的每个方法前面打印一行log,输出当前执行的是哪个方法,
@Aspect publicclassAspectTest{ privatestaticfinalStringTAG="AspectTest"; @Pointcut("execution(*phoenix.com.helloaspectj.MainActivity.**(..))") publicvoidexecuteAspectJ(){ } @Before("executeAspectJ()") publicvoidbeforeAspectJ(JoinPointjoinPoint)throwsThrowable{ Log.d(TAG,"beforeAspectJ:injected->"+joinPoint.toShortString()); } }
第一次接触AspectJ的看到这段代码有点摸不着头脑,解释一下几个注解的意思,
- @Aspect:告诉ACJ编译器这是个AspectJ类
- @PointCut:PointCut是AspectJ中的一个概念,跟它一起的另一个概念是JoinPoint,这两个概念一起描述要注入的切面
- @Before:表示要注入的位置,常用的有Before/After/Around,分别表示在执行前,执行后,和取代原方法
这里@PointCut注解后的参数表示的意思是对MainActivity中的所有方法进行注入,参数用的是正则匹配语法。
下面看看这段代码执行的结果
07-2616:04:56.61122823-22823/?D/AspectTest:beforeAspectJ:injected->execution(MainActivity.onCreate(..))
07-2616:04:56.66122823-22823/?D/AspectTest:beforeAspectJ:injected->execution(MainActivity.onStart())
看到虽然我们没有在MainActivity中写入log打印语句,但是通过AspectJ实现了,在MainActivity两个生命周期执行前插入了我们自己的log。
使用场景
AspectJ只是AOP的其中一种手段,类似的还有用asm去修改字节码。AOP之所以会有越来越多的人去了解,抽象上来说它可以非常好的去耦合。
高级点的可以用AOP来实现无痕埋点,数据收集,甚至修改SDK中动不了的代码。
上面的整个DEMO代码可以从GitHub上获取,后台回复"切面"就可以获取下载链接。