Android面向切面AOP架构设计简析

按照惯例,谈一个框架时我们先说明一下这东西到底是啥、干什么的,首先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();
}
主页.png

然后我们看下下面按钮的点击方法处理

@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();
    }
}

当没有登录时的日志输出和显示页面为


未登陆时的打印.png

登录页面.png

然后点击上面的按钮,手动将变量设置为true


登录后的日志输出.png

相信看到这里大家应该有了比较清晰的认识,已经晚上11:42了,明天还要上班,就不深入说明这个框架了,明后两天再深入剖析一下框架的用法和实现原理。

你可能感兴趣的:(Android面向切面AOP架构设计简析)