安卓开发学习之AspectJ初探

AOP面向切面编程近几年挺火的,用处大概就是为了让多个模块拥有同一个功能(比如执行同一个方法),如果用面向对象,就相当麻烦,但如果用AOP(比如用AspectJ),利用注解就可以实现,下面我演示一下使用步骤。


准备工作:


    1、下载jar包 aspectj-1.8.5.jar,下载后打开它,安装,下一步到底就中。


    2、新开一个安卓项目,配置gradle。我把我的贴出来,大伙儿直接CV就中。

apply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.8.9'
        classpath 'org.aspectj:aspectjweaver:1.8.9'
    }
}
repositories {
    mavenCentral()
}
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;
            }
        }
    }
}

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.songzeceng.studyofaop"
        minSdkVersion 24
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}



dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.1.0'
    testCompile 'junit:junit:4.12'
    compile files('libs/aspectjrt.jar')
}

    唯独记得android标签下的compileSdkVersion、applicationId这些要是自己的,莫忘了改


    3、而后把aspectjrt.jar放到项目目录下的libs目录中(aspectjrt.jar在用户目录下aspectj1.8/lib中)


这样准备工作就完成了,下面我们正儿八经开始写代码


    1、创建一个注解,做为向aspectj传递数据的"intent意图",这个意图里的值,我们定义一个方法value来设置

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value();
}

       可见,这个注解在方法上、运行时生效。于是,被它标记的方法,我们就可以用来给aspectj进行数据的传输


    2、我们在主活动中定义一个方法test(),用来检查网络权限的赋予与否,用上我们的注解

@MyAnnotation(value = Manifest.permission.INTERNET)
private int test() {
    Log.i(TAG, "网络权限检查");
    return 0;
}

    3、然后是我们的核心类MyAspectJ,做为触发切面时的回调。

@Aspect
public class MyAspectJ {
    private static final String TAG = "MyAspectJ";

    @Pointcut("execution(@com.example.songzeceng.studyofaop.MyAnnotation * *(..))")
    public int executeAj() {
        Log.i(TAG, "executeAj()......");
        return 1;
    }
    ...
}

        @AspectJ注解:给我们的类标上它,当切面被触发时,就会执行这个类里面的一些操作

        @Pointcut注解:切点,也就是触发条件。

                               这里头有参数,我解释一下:

                                   execution():表示执行时触发切面。

                                   @....MyAnnotation:执行这个注解时(也就是遇到@Annotation,就触发)

                                   *:任意包名

                                   *(..):任意返回值,任意参数列表

    

    4、然后介绍仨注解@Before,@Around,@After

         这三个注解使用时,都要传入相对的是哪个方法的方法名

@Before("executeAj()")
public void before () {
    Log.i(TAG, "before executeAj()....");
}
@Around("executeAj()")
public Object aroundExecuteAj(ProceedingJoinPoint joinPoint) throws Throwable {
    Log.i(TAG, "around executeAj()....");

    Object o = null;
    ....
    return o;
}
@After("executeAj()")
public void after () {
    Log.i(TAG, "after executeAj()....");
}

       使用注解的方法的方法名、返回值、参数列表都无所谓。

       @Around是替换的意思,也就是说,aroundExecuteAj()使用了@Around("executeAj()"),那么,executeAj()将不会被执行,转而执行aroundExecuteAj()。故而,执行test()的时候,执行的是aroundExecuteAj()    

      @Before望文生义,是在执行test()之前,被调用的方法。但这里的执行,不是说在活动里直接调用test(),而是调用ProceedingJoinPoint.proceed()之前。

        @After则是在切点@Pointcut方法或@Around方法执行完之后执行的        


    5、给aroundExecuteAj()传入ProceedJoinPoint参数。

     方才我们说了@Before和@After的前后,是ProceedJoinPoint.proceed()的前后,我们就通过传参的方式获取ProceedJoinPoint对象,并调用proceed()方法

@Around("executeAj()")
public void aroundExecuteAj(ProceedingJoinPoint joinPoint) throws Throwable {
    Log.i(TAG, "around executeAj()....");

    Object o = null;

    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    MyAnnotation myAnnotation = methodSignature.getMethod().getAnnotation(MyAnnotation.class); // 反射
    String permission = myAnnotation.value();
    Context context = (Context) joinPoint.getThis();

    if (context.checkPermission(permission, android.os.Process.myPid(), android.os.Process.myUid()) == PackageManager.PERMISSION_GRANTED) {
        o = joinPoint.proceed();
        Log.i(TAG, "权限已被授予....");
    } else {
        Log.i(TAG, "权限未被授予....");
    }

    if (o != null) {
        Log.i(TAG, "o is " + o.getClass().getCanonicalName());
    }
    ...
}

         ProceedingJoinPoint对象里面保存着我们的触发切面的方法test()的一些信息,在处理过程中,我们通过反射获取自定义注解@MyAnnotation里的value()方法返回值(也就是网络权限),以及调用test()的对象(joinPoint.getThis()方法)

        而后就是重点了,正儿八经在切面回调里调用test()方法,是joinPoint.proceed()方法,而@Before方法,在这一步之前被调用,@After方法却是在@Around方法执行完以后执行的。

        另外,joinPoint.proceed()方法的返回值,就是test()的返回值。


    6、打印其他信息      

Log.i(TAG, "joinPoint`s kind :" + joinPoint.getKind());
Log.i(TAG, "joinPoint`s signature :" + joinPoint.getSignature().getName());
Log.i(TAG, "joinPoint`s source location :" + joinPoint.getSourceLocation().getFileName());
Log.i(TAG, "joinPoint`s toLongString() :" + joinPoint.toLongString());

        kind是@Pointcut的信息(execution则是方法执行时,即method-execution)

        signature是test()的方法名

        sourceLocation是test()方法在哪个类

        toLongString():@Pointcut的详细信息


        输出结果:



        最后,我贴一下joinPoint.proceed()方法执行和不执行时,@Before,@Around,@After和test的执行情况

        执行joinPoint.proceed():



        不执行joinPoint.proceed()方法:



好了,最基础aspectJ就是这样了。可以看到,用上了@MyAnnotation注解的方法都会触发切面,给开发带来的方便确实不是一点半点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值