在我们进行框架设计的时候,我们会给系统分模块,会画出框图来对系统进行介绍,使功能边界更清晰。但是有些特殊的功能,却没有办法归类到某一个模块,即使归类到某一个模块,这个模块与其他模块之间的耦合也会非常的深。比如:用户行为统计、检测用户是否登录。这种特殊功能会在整个系统的方方面面出现,如果我们只是用传统的方式,京用户行为统计作为一个模块,然后提供出API让其他模块使用,那么其他模块中将遍布这个特殊模块的API。这是我们不想看到的。因为这个耦合程度太大太深。
如果说用面向对象OOP分模块的思想不能解决上述的问题,那么是否有其他的方法论可以解决这个问题呢?当然,面向切面编程AOP可以很好地解决这个问题。
当我们学习一个方法论时,最重要的也是最难的部分就是学习它的思想,AOP思想就是把一个系统划分为多个切面。而我们只需要在某一个切面上去处理问题,而不会影响到其他切面的功能。
下面我们就用一个Android中统计用户点击事件的例子来说明面向切面编程。
我们这里要用到一个叫做AspectJ的框架来帮我们实现面向切面编程。AspectJ定义了一个规范,并遵循Java编译规范,给我们使用javac编译的class增加了一部分内容。Aspect不会修改你的Java文件,而是在你将Java文件编译成class文件时,修改了class文件。
Aspects有三个概念:aspect(切面)、pointcut(切点)、Advice(处理,包括Around、Before、After)
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
,