面向切面的程序设计:通过横切关注点与业务主体进行一步分离,以提高程序代码的模块程度。
举例来说,比如超级会员、会员、普通用户登录一个系统,那么他们都要使用到登录业务,这时就可以利用横向切面将整个业务分割出来,并植入一些代码,达到统一管理的效果。在Android开发中,可以使用AspectJ框架来实现。
ASpectJ有2个重要的概念:PointCut(切入点)、JointPoint(连接点)切入点,就是要执行某个方法之前,抢先一步在这个方法之前切入并执行一些代码,而这些代码就可以通过JointPoint来完成。
AspectJ主要有3步:
举例:在登录界面点击个人主页,购物车时记录用户的行为信息
打开Android Studio,首先要引入AspectJ, 修改app的build.gradle
apply plugin: 'com.android.application'
buildscript { //编译使用Aspect的编译器,不再使用传统的javac
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.example.aspectj_logindemo"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'org.aspectj:aspectjrt:1.8.13'
}
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就成功引进了!
接下来完成用户行为的统计功能,简单界面如图:

当点击”个人主页“和”购物车的时候“记录用户的行为,这些用户信息对开发商很重要。
新建一个自定义注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClickEvent {
String value();
}
为按钮方法注释注解:
@ClickEvent("login")
public void login(View view) {
Log.d(TAG, "login: ");
SharedPreferences.Editor editor = getSharedPreferences("share", Context.MODE_PRIVATE).edit();
editor.putBoolean(LOGIN, true);
editor.apply();
startActivity(new Intent(this, LoginActivity.class));
}
@ClickEvent("personal_page")
public void personal_page(View view) {
Log.d(TAG, "personal_page: ");
startActivity(new Intent(this, OtherActivity.class));
}
@ClickEvent("cart")
public void cart(View view) {
Log.d(TAG, "cart: ");
startActivity(new Intent(this, OtherActivity.class));
}
这些注解将成为切入点,即:获取所有被注解的方法。
新建一个切面类:
@Aspect
public class ClickAspect {
}
只要为新建的类添加@Aspect注解,哪个这个类就成为切面类了,类名无所谓。
在类中设置切入点:
@Pointcut("execution(@com.example.aspectj_logindemo.ClickEvent * *(..))")
public void methodPointCut(){}
表示被ClickEvent所注解的方法都将成为切入点
然后对切入点进行处理:
@Around("methodPointCut()")
public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable{
.....
}
这个ProceedingJoinPoint对象就是切入点方法,它有个方法是proceed,当ProceedingJoinPoint调用proceed方法时,会等价于调用所切入点切入的方法。
比如当点击登录按钮时,Aspect就会切入这个对应的login()方法,如果:
public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable{
Log.D(TAG, "before method start");
return joinPoint.proceed();
Log.D(TAG, "method ending");
}
那么终端就会打印:
before method start
login
method ending
这样一来AOP的效果就达到了!
所以获取用户行为信息就可以写成:
@Around("methodPointCut()")
public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable{
Log.d(TAG, "jointPoint: ");
//获取方法所属的类名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className = methodSignature.getDeclaringType().getSimpleName();
//获取方法名
String methodName = methodSignature.getName();
ClickEvent annotation = methodSignature.getMethod().getAnnotation(ClickEvent.class);//获取方法的注解
String annotationValue = null;
if(annotation != null) {
annotationValue = annotation.value(); //获取方法的注解值
}
long start= System.currentTimeMillis();
Log.d(TAG, "jointPoint: >>>>> Method start");
Object result = joinPoint.proceed(); //此时对应的方法会执行
long end = System.currentTimeMillis();
Log.d(TAG, "jointPoint: >>>>> Method end");
//通常的项目是把这些值保存到数据库中,然后在某个时间点(定时1天或者1星期后)发送远程服务器这些开发商才能用来记录用户的行为,这里直接就通过log打印了
Log.d(TAG, "jointPoint: "+String.format("类名%s,方法名%s,注解值%s, 方法执行时间%d", className,
methodName, annotationValue, end - start));
return result;
}
demo地址:https://download.youkuaiyun.com/download/weixin_45253393/12329619