在我们进行框架设计的时候,我们会给系统分模块,会画出框图来对系统进行介绍,使功能边界更清晰。但是有些特殊的功能,却没有办法归类到某一个模块,即使归类到某一个模块,这个模块与其他模块之间的耦合也会非常的深。比如:用户行为统计、检测用户是否登录。这种特殊功能会在整个系统的方方面面出现,如果我们只是用传统的方式,京用户行为统计作为一个模块,然后提供出API让其他模块使用,那么其他模块中将遍布这个特殊模块的API。这是我们不想看到的。因为这个耦合程度太大太深。

如果说用面向对象OOP分模块的思想不能解决上述的问题,那么是否有其他的方法论可以解决这个问题呢?当然,面向切面编程AOP可以很好地解决这个问题。

当我们学习一个方法论时,最重要的也是最难的部分就是学习它的思想,AOP思想就是把一个系统划分为多个切面。而我们只需要在某一个切面上去处理问题,而不会影响到其他切面的功能。

安卓activity入门教程(AndroidAOP工具之AspectJ实战)(1)

下面我们就用一个Android中统计用户点击事件的例子来说明面向切面编程。

我们这里要用到一个叫做AspectJ的框架来帮我们实现面向切面编程。AspectJ定义了一个规范,并遵循Java编译规范,给我们使用javac编译的class增加了一部分内容。Aspect不会修改你的Java文件,而是在你将Java文件编译成class文件时,修改了class文件。

Aspects有三个概念:aspect(切面)、pointcut(切点)、Advice(处理,包括Around、Before、After)

安卓activity入门教程(AndroidAOP工具之AspectJ实战)(2)


Android Studio下配置Aspects有两种方式。

使用hujiang开源插件(这种方式比较简单,并且兼容kotlin)

在项目根目录的build.gradle文件中引用插件:

buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.5.3' classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.6' } }

在所要运行的module目录下的build.gradle中使用插件,并增加依赖库,aspectjrt依赖库在运行时需要使用到:

apply plugin: 'android-aspectjx' ... dependencies { ... compile 'org.aspectj:aspectjrt:1.8.10' }

自己接入AspectJ

在项目根目录build.gradle下引入aspectjtools插件:

buildscript { dependencies { .. classpath 'org.aspectj:aspectjtools:1.8.10' classpath 'org.aspectj:aspectjweaver:1.8.8' } }

在运行app的module目录下的build.gradle中引入:

import org.aspectj.bridge.Imessage import org.aspectj.bridge.MessageHandler import org.aspectj.tools.ajc.Main 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; } } } }

注意:这个时候,依赖aspectj的library下的build.gradle也需要做相应的修改,否则的话可能会报 NoSuchMethodError: Aspect.aspectOf 异常。

dependencies { ... // 以api的方式提供引入library的一方,这样外界就不用再去引入aspectjrt了 // 如果是library私有的,则外界需要引入aspectjrt,否则在编译的时候不会对织入点做处理 api 'org.aspectj:aspectjrt:1.8.10' } import org.aspectj.bridge.IMessage import org.aspectj.bridge.MessageHandler import org.aspectj.tools.ajc.Main 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", 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; } } } }


实现对点击事件进行监听处理

@Aspect public class ClickAspect { final String TAG = ActivityAspect.class.getSimpleName(); @Around("execution(* android.view.View.OnClickListener.onClick(..))") public void onClickMethodAround(ProceedingJoinPoint joinPoint) throws Throwable { Object[] args = joinPoint.getArgs(); View view = null; for (Object arg : args) { if (arg instanceof View) { view = (View) arg; } } String resEntryName = null; String resName = null; if (view != null) { try { resEntryName = view.getContext().getResources().getResourceEntryName(view.getId()); resName = view.getContext().getResources().getResourceName(view.getId()); } catch (Resources.NotFoundException e) { e.printStackTrace(); } } joinPoint.proceed(); Log.d(TAG, "after onclick: " "resEntryName: " resEntryName " resName: " resName); } }

其中,@Aspect注解表明这个类是一个切面。@Around注解表示在某一类方法执行的前后处理。除此之外,我们还可以用@Before和@After注解分别表示在某一类方法之前之前和之后。joinPoint.proceed()表示方法执行。


在切面中排除某些模块

在运行中,发现程序运行不通过,原因是某些模块不支持,我们需要将它们排除在外。通过配置模块的build.gradle文件:

aspectjx{ exclude 'com.alibaba' }

以上就是Aspect的初步使用,还是比较简单的,其实Aspect还有其他比较复杂的内容。我们用到了再去看即可。

登录状态检测也是应用模块中普遍需要使用的。对于用面向切面编程来检测登录状态,没有登录则需要用户登录的功能。可以参考以下开源代码:

https://github.com/sososeen09/android-blog-demos/tree/master/aop-tech

,