按照惯例,谈一个框架时我们先说明一下这东西到底是啥、干什么的,首先AOP面向切面和我们通常意义上写的代码不太一样,Java是OOP面向对象,所有的代码都是符合某个功能的,是分门别类好的,但是我们在实际的安卓开发过程中OOP的设计思想是比较难处理一些问题的,比如模块埋点、鉴权以及一些简单但是重复性比较高的代码,如我们要查看个人资料页面就必须先登录,查看个人消息也需要登录。
if(isLogin){
你的业务逻辑
}else{
打开登录页面
}
像上面的这种代码会大量的出现在我们的项目中,当然这是比较不太优雅的实现方法,还有像代码埋点,如果说用户登录这个还能勉强做个工具类,但是埋点就真的是毫无办法了,这个时候我们用到AOP就能够优雅的解决问题了。
这个时候就有必要提到一个框架AspectJ,它可以在代码编译期插入代码来实现你的业务需求,这是我的理解,当然如果在网上复制一大段关于它的描述没意思,概念都不是人看的,直接上代码看运行效果,相信大家会有一个比较清晰的认识。
首先我们需要对Gradle进行配置
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
// 下面两个是框架的依赖
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
上面是Project的gradle的依赖,以下是app的gradle配置
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "demo.zhongshi.com.myapplication"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
buildToolsVersion '28.0.2'
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
// 只需下一行的依赖
implementation 'org.aspectj:aspectjrt:1.8.9'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
// 下面是AspectJ的编译配置文件
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger
android.applicationVariants.all{ variant ->
JavaCompile javaCompile = variant.javaCompiler
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
}
}
}
}
众所周知,我们用javac可以把java编译成class文件,当然我们如果一个类只写了一行代码,class文件是不会生成其他无关的字节码的,这是常识,但是AspectJ是可以生成属于自己规则的字节码,它是遵循java编译的规则并做了自己的处理,所使用的编译器是 Ajc,整个的使用步骤可以分为三个部分,切点、切面、处理,我们先通过一个小demo来看下它的具体玩法,然后再谈这个问题。
新建一个注解,标识为运行期作用,用于对方法切点
/**
* 利用注解来切点标示方法,作用在运行期
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckLogin {
String value();
}
然后我们看下下面按钮的点击方法处理
@CheckLogin("startPersonalData")
private void startPersonalData() {
Log.e("--->","进入个人资料页面");
}
最后是AspectJ对切点和切面的处理
@Aspect
public class CheckLoginAspectJ {
/**
* 找到指定注解的切点
*/
@Pointcut("execution(@demo.zhongshi.com.myapplication.CheckLogin * *(..))")
public void executeCheckLogin(){}
/**
* 切面
* @param point
* @return
* @throws Throwable
*/
@Around("executeCheckLogin()")
public Object checkLogin(ProceedingJoinPoint point) throws Throwable{
MethodSignature signature = (MethodSignature) point.getSignature();
CheckLogin checkLogin = signature.getMethod().getAnnotation(CheckLogin.class);
if(null != checkLogin){
boolean isLogin = Constants.isLogin();
if(isLogin){
Log.e("--->", "当前已登录");
return point.proceed();
}else {
Log.e("--->", "请登陆账号");
Context context = (Context) point.getThis();
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent);
return null;
}
}
Log.e("--->", "注解为空");
return point.proceed();
}
}
当没有登录时的日志输出和显示页面为
然后点击上面的按钮,手动将变量设置为true
相信看到这里大家应该有了比较清晰的认识,已经晚上11:42了,明天还要上班,就不深入说明这个框架了,明后两天再深入剖析一下框架的用法和实现原理。