方式二:预编译,利用AspectJ
本文先用AS 3.6.1 ,gradle 5.6.4-all试试看
AspectJ的使用核心就是它的编译器,它就做了一件事,将AspectJ的代码在编译期插入目标程序当中,运行时跟在其它地方没什么两样,因此要使用它最关键的就是使用它的编译器去编译代码ajc。ajc会构建目标程序与AspectJ代码的联系,在编译期将AspectJ代码插入被切出的PointCut中,已达到AOP的目的。
因此,无论在什么IDE上(如果使用命令行就可以直接使用ajc编译了),问题就是让IDE使用ajc作为编译器编译代码。
主要是环境的配置步骤,这里具体说一下:
添加完之后的build.gradle文件这样:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.1'
//预编译实现AOP
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
完成之后的完整build.gradle文件:
apply plugin: 'com.android.application'
//预编译实现AOP
buildscript {
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.source.loginaop"
minSdkVersion 22
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.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'org.aspectj:aspectjrt:1.8.13'
}
// ============拷AspectJ的构建代码BEGIN============
// 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境)
// 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告)
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的构建代码END============
实现的效果:
使用AspectJ技术,点击我的积分和我的优惠券会进行行为统计和登陆状态判断,如果没登陆,则进入登陆页面,已经登陆则进入到对应的详情里面去。
接下来就直接贴代码了:
项目结构:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@BehaviorCount("登录")
public void login(View view) {
startActivity(new Intent(this, LoginActivity.class));
}
//普通方式
/*public void score(View view) {
if (LoginUtils.isLogin()) {
startActivity(new Intent(this, ContentActivity.class));
} else {
Toast.makeText(this, "请先登录", Toast.LENGTH_LONG).show();
startActivity(new Intent(this, LoginActivity.class));
}
}*/
//普通方式
/*public void coupon(View view) {
if (LoginUtils.isLogin()) {
startActivity(new Intent(this, ContentActivity.class));
} else {
Toast.makeText(this, "请先登录", Toast.LENGTH_LONG).show();
startActivity(new Intent(this, LoginActivity.class));
}
}*/
//使用AOP进行改造
@BehaviorCount("积分")
//@LoginCheck
public void score(View view) {
startActivity(new Intent(this, ContentActivity.class));
}
@BehaviorCount("优惠券")
@LoginCheck
public void coupon(View view) {
startActivity(new Intent(this, ContentActivity.class));
}
}
/**
* @author Eason
* @createtime 2020/3/17
* @desc 行为统计注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BehaviorCount {
String value();
}
/**
* @author Eason
* @createtime 2020/3/17
* @desc 登录判断注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginCheck {
}
/**
* @author Eason
* @createtime 2020/3/17
* @desc 定义切面
*/
@Aspect
public class BehaviorCountAspect {
String TAG = getClass().getSimpleName();
// 1、应用中用到了哪些注解,放到当前的切入点进行处理(找到需要处理的切入点)
// execution:以方法执行时作为切点,触发Aspect类
// * *(..) :可以处理ClickBehavior这个类所有的方法
@Pointcut("execution(@com.source.loginaop.annotation.BehaviorCount * *(..))")
public void methodPointCut() {
}
// 2、对切入点如何处理
@Around("methodPointCut()")
public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable {
Log.d(TAG, "BehaviorCount jointPoint....");
Context context = (Context) joinPoint.getThis();
//获取方法签名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//获取方法所在类名
String className = methodSignature.getDeclaringType().getSimpleName();
//获取方法名
String methodName = methodSignature.getName();
//获取方法的注解值
String value = methodSignature.getMethod().getAnnotation(BehaviorCount.class).value();
// 统计方法的执行时间、统计用户点击某功能行为。(存储到本地,每过x天上传到服务器)
long begin = System.currentTimeMillis();
Log.e(TAG, "ClickBehavior Method Start >>> ");
Object proceed = joinPoint.proceed();
long duration = System.currentTimeMillis() - begin;
Log.e(TAG, "ClickBehavior Method End >>> ");
Log.e(TAG, String.format("统计了:%s功能,在%s类的%s方法,用时%d ms",
value, className, methodName, duration));
return proceed;
}
}
/**
* @author Eason
* @createtime 2020/3/17
* @desc 定义切面
*/
@Aspect
public class LoginCheckAspect {
String TAG = getClass().getSimpleName();
// 1、应用中用到了哪些注解,放到当前的切入点进行处理(找到需要处理的切入点)
// execution:以方法执行时作为切点,触发Aspect类
// * *(..) :可以处理ClickBehavior这个类所有的方法
@Pointcut("execution(@com.source.loginaop.annotation.LoginCheck * *(..))")
public void methodPointCut() {
}
// 2、对切入点如何处理
@Around("methodPointCut()")
public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable {
Log.d(TAG, "LoginCheck jointPoint....");
Context context = (Context) joinPoint.getThis();
if (LoginUtils.isLogin()) {
return joinPoint.proceed();
} else {
Toast.makeText(context, "请先登录", Toast.LENGTH_LONG).show();
context.startActivity(new Intent(context, LoginActivity.class));
// 不再执行方法(切入点)
return null;
}
}
}
切面编程可以用于行为统计、网络状态判断提示、登陆判断等场景。
END.