Android收藏集

Android 架构17 AOP 面向切面编程

2018-08-11  本文已影响3人  锐心凌志

引言

相信很多做过Web的同学对AspectJ都不陌生,Spring的AOP就是基于它而来的。如果说平常我们随便写写程序的时候,基本也不会用到它,需要调试的话无非就是多加一个System.out.printfln()或者Log.d()。但是由于基于面向对象的固有缺陷,导致很多同模块、同一水平上的工作要在许多类中重复出现。比如说:输出日志,监控方法执行时间,修改程序运行时的参数等等这样的事情,其实它们的代码都是可以重用的。

如果在一个大型的项目当中,使用手动修改源码的方式来达到调试、监控的目的,第一,需要插入许多重复代码(打印日志,监控方法执行时间),代码无法复用;第二,修改的成本太高,处处需要手动修改(分分钟累死、眼花)。

1.AspectJ介绍

1.1 AspectJ只是一个代码编译器

AspectJ 意思就是Java的Aspect,Java的AOP。它其实不是一个新的语言,它就是一个代码编译器(ajc,后面以此代替),在Java编译器的基础上增加了一些它自己的关键字识别和编译方法。因此,ajc也可以编译Java代码。它在编译期将开发者编写的Aspect程序编织到目标程序中,对目标程序作了重构,目的就是建立目标程序与Aspect程序的连接(耦合,获得对方的引用(获得的是声明类型,不是运行时类型)和上下文信息),从而达到AOP的目的(这里在编译期还是修改了原来程序的代码,但是是ajc替我们做的)。

1.2 AspectJ是用来做AOP编程的

Cross-cutting concerns(横切关注点): 尽管面向对象模型中大多数类会实现单一特定的功能,但通常也会开放一些通用的附属功能给其他类。例如,我们希望在数据访问层中的类中添加日志,同时也希望当UI层中一个线程进入或者退出调用一个方法时添加日志。尽管每个类都有一个区别于其他类的主要功能,但在代码里,仍然经常需要添加一些相同的附属功能。

下面这张图简要总结了一下上述这些概念。

传统编程:逐个插入验证用户模块

AOP方案:关注点聚焦

1.3、为什么要用AspectJ?

2、下载AspectJ相关资源与build.gradle配置

2.1、下载地址

下载aspectj的地址http://www.eclipse.org/aspectj/downloads.php

2.2、解压aspectj jar包得到aspectjrt.jar
2.3、build.gradle配置

参考build.gradle aspectJ 写法 http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/
根目录中build.gradle配置:

buildscript {

    repositories {
        mavenCentral()
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
        classpath 'org.aspectj:aspectjtools:1.8.13'
        classpath 'org.aspectj:aspectjweaver:1.8.13'

        // 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
}

modules中build.gradle配置:

apply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.haocai.aopdemo"
        minSdkVersion 15
        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'
        }
    }
}

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;
            }
        }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    compile files('libs/aspectjrt.jar')
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
   // compile 'org.aspectj:aspectjrt:1.8.+'
}

注意:

dependencies 不要忘记添加 compile files('libs/aspectjrt.jar') ,aspectjrt.jar就是上一步解压得到的文件,放到libs文件夹下

3、示例程序

创建注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface BehaviorTrace {
    String value();
    int type();
}

Aspect 类
/**
 * Created by Xionghu on 2018/1/23.
 * Desc: 切面
 * 你想要切下来的部分(代码逻辑功能重复模块)
 */
@Aspect
public class BehaviorAspect {
    private static final String TAG = "MainAspect";
    SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 根据切点 切成什么样子
     *
     */
    @Pointcut("execution(@com.haocai.aopdemo.BehaviorTrace * *(..))")
    public void annoBehavior() {

    }
    /**
     * 切成什么样子之后,怎么去处理
     *
     */

    @Around("annoBehavior()")
    public Object dealPoint(ProceedingJoinPoint point) throws Throwable{
        //方法执行前
        MethodSignature methodSignature = (MethodSignature)point.getSignature();
        BehaviorTrace behaviorTrace = methodSignature.getMethod().getAnnotation(BehaviorTrace.class);
        String contentType = behaviorTrace.value();
        int type = behaviorTrace.type();
        Log.i(TAG,contentType+"使用时间:   "+simpleDateFormat.format(new Date()));
        long beagin=System.currentTimeMillis();
        //方法执行时
        Object object = null;
        try{
            object = point.proceed();
        }catch (Exception e){
            e.printStackTrace();
        }

        //方法执行完成
        Log.i(TAG,"消耗时间:"+(System.currentTimeMillis()-beagin)+"ms");
        return object;
    }
}

调用主程序

package com.haocai.aopdemo;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Main";
    SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }
    /**
     * 摇一摇的模块
     *
     * @param view
     */
    @BehaviorTrace(value = "摇一摇",type = 1)
    public  void mShake(View view)
    {
        //摇一摇的代码逻辑
        {
            SystemClock.sleep(3000);
            Log.i(TAG," 摇到一个红包");

        }
    }
    /**
     * 语音的模块
     *
     * @param view
     */
    @BehaviorTrace(value = "语音:",type = 1)
    public  void mAudio(View view)
    {
        //语音代码逻辑
        {
            SystemClock.sleep(3000);
            Log.i(TAG,"发语音:我要到一个红包啦");
        }
    }
    /**
     * 打字模块
     *
     * @param view
     */
    @BehaviorTrace(value = "打字:",type = 1)
    public  void mText(View view)
    {
        //打字模块逻辑
        {
            SystemClock.sleep(3000);
            Log.i(TAG,"打字逻辑,我摇到了一个大红包");

        }

    }

//    /**
//     * 摇一摇的模块
//     *
//     * @param view
//     */
//    @BehaviorTrace(value = "摇一摇",type = 1)
//    public  void mShake(View view)
//    {
//            SystemClock.sleep(3000);
//            Log.i(TAG,"  摇到一个嫩模:  约不约");
//    }
//
//    /**
//     * 摇一摇的模块
//     *
//     * @param view
//     */
//    public  void mShake(View view)
//    {
//
//        long beagin=System.currentTimeMillis();
//        Log.i(TAG,"摇一摇:  使用时间:   "+simpleDateFormat.format(new Date()));
//        //摇一摇的代码逻辑
//        {
//            SystemClock.sleep(3000);
//
//            Log.i(TAG," 摇到一个红包");
//
//        }
//        //事件统计逻辑
//        Log.i(TAG,"消耗时间:  "+(System.currentTimeMillis()-beagin)+"ms");
//
//    }

//    /**
//     * 语音的模块
//     *
//     * @param view
//     */
//    public  void mAudio(View view)
//    {
//        long beagin=System.currentTimeMillis();
//        Log.i(TAG,"语音:  使用时间:   "+simpleDateFormat.format(new Date()));
//        //语音代码逻辑
//        {
//            SystemClock.sleep(3000);
//
//            Log.i(TAG,"发语音:我要到一个红包啦");
//
//        }
//       //事件统计逻辑
//        Log.i(TAG,"消耗时间:  "+(System.currentTimeMillis()-beagin)+"ms");
//    }
//
//    /**
//     * 打字模块
//     *
//     * @param view
//     */
//    public  void mText(View view)
//    {
//        //统计用户行为 的逻辑
//        Log.i(TAG,"文字:  使用时间:   "+simpleDateFormat.format(new Date()));
//        long beagin=System.currentTimeMillis();
//
//        //打字模块逻辑
//        {
//            SystemClock.sleep(3000);
//            Log.i(TAG,"打字逻辑,我摇到了一个大红包");
//
//        }
//        //事件统计逻辑
//        Log.i(TAG,"消耗时间:  "+(System.currentTimeMillis()-beagin)+"ms");
//    }

}

注意:下面注释部分为传统写法
运行结果
01-23 19:39:09.579 13051-13051/com.haocai.aopdemo I/MainAspect: 摇一摇使用时间:   2018-01-23 19:39:09
01-23 19:39:12.589 13051-13051/com.haocai.aopdemo I/Main:  摇到一个红包
01-23 19:39:12.589 13051-13051/com.haocai.aopdemo I/MainAspect: 消耗时间:3001ms
01-23 19:39:12.589 13051-13051/com.haocai.aopdemo I/MainAspect: 语音:使用时间:   2018-01-23 19:39:12
01-23 19:39:15.599 13051-13051/com.haocai.aopdemo I/Main: 发语音:我要到一个红包啦
01-23 19:39:15.599 13051-13051/com.haocai.aopdemo I/MainAspect: 消耗时间:3000ms
01-23 19:39:15.599 13051-13051/com.haocai.aopdemo I/MainAspect: 打字:使用时间:   2018-01-23 19:39:15
01-23 19:39:18.609 13051-13051/com.haocai.aopdemo I/Main: 打字逻辑,我摇到了一个大红包
01-23 19:39:18.609 13051-13051/com.haocai.aopdemo I/MainAspect: 消耗时间:3000ms

源码下载
Github:https://github.com/kpioneer123/AopDemo
上一篇下一篇

猜你喜欢

热点阅读