AOP 实现埋点统计 和 统一登录

子曰:温故而知新,可以为师矣。 《论语》-- 孔子



一、概念

  • AOPAspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。


二、AspectJ

1. 介绍:

  • 面向切面编程的框架,是对 Java 的扩展,而且完全兼容 java 。它定义了 AOP 语法,有一个专门的编译器用来生成遵守 Java 字节码编码规范的 Class 文件, 还支持原生的 Java,只需要加上 AspectJ 提供的注解即可。

2. 术语

  • Joinpoint(连接点):指那些被拦截到的点。
  • PointCut(切入点):指我们要对哪些 Joinpoint 进行拦截的定义。
  • Advice(通知/增强):值拦截到 Joinpoint 之后需要做的事情。
  • Introduction(引介):一种特殊的通知,在不修改类代码的前提下。
  • Target(目标对象):代理的目标对象。
  • Weaving(织入):把增强应用到目标对象来创建新的代理对象的过程。
  • Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。
  • Aspect(切面):是切入点和通知(引介)的结合。

3. 实操

1. 根目录以及项目的 .gradle 文件添加依赖。
// 1. 根目录 .gradle文件
classpath 'org.aspectj:aspectjtools:1.9.4'
// 2. 项目 .gradle文件
implementation 'org.aspectj:aspectjrt:1.9.4'
// 注意  minSdkVersion 24 

// 3. 项目 .gradle文件 最后添加以下代码,用于打印日志。
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.9",
                         "-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;
            }
        }
    }
}

2. 自定义注解
// 用户登录检测 LoginCheck 类
@Target(ElementType.METHOD) // 目标作用在方法之上
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginCheck {
}

// 用户点击痕迹(行为统计)  ClickBehavior 类
@Target(ElementType.METHOD) // 目标作用在方法之上
@Retention(RetentionPolicy.RUNTIME)
public @interface ClickBehavior {

    String value();
}

3. 方法上加上自定义注解
// 登录点击事件(用户行为统计)
    @ClickBehavior("登录")
    public void login(View view) {
        Log.e(TAG, "模拟接口请求……验证通过,登录成功!");
    }

    // 用户行为统计(友盟统计?!后台要求自己统计)
    @ClickBehavior("我的专区")
    @LoginCheck
    public void area(View view) {
        Log.e(TAG, "开始跳转到 -> 我的专区 Activity");
        startActivity(new Intent(this, OtherActivity.class));
    }

4. 编写 Aspect 界面
Aspect // 定义切面类
public class ClickBehaviorAspect {

    private final static String TAG = "TAG";

    // 1、应用中用到了哪些注解,放到当前的切入点进行处理(找到需要处理的切入点)
    // execution,以方法执行时作为切点,触发Aspect类
    // * *(..)) 可以处理ClickBehavior这个类所有的方法
    @Pointcut("execution(@com.kww.aopdmo.ClickBehavior * *(..))")
    public void methodPointCut() {}

    // 2、对切入点如何处理
    @Around("methodPointCut()")
    public Object jointPotin(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取签名方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        // 获取方法所属的类名
        String className = methodSignature.getDeclaringType().getSimpleName();

        // 获取方法名
        String methodName = methodSignature.getName();

        // 获取方法的注解值(需要统计的用户行为)
        String funName = methodSignature.getMethod().getAnnotation(ClickBehavior.class).value();

        // 统计方法的执行时间、统计用户点击某功能行为。(存储到本地,每过x天上传到服务器)
        long begin = System.currentTimeMillis();
        Log.e(TAG, "ClickBehavior Method Start >>> ");
        Object result = joinPoint.proceed(); // MainActivity中切面的方法
        long duration = System.currentTimeMillis() - begin;
        Log.e(TAG, "ClickBehavior Method End >>> ");
        Log.e(TAG, String.format("统计了:%s功能,在%s类的%s方法,用时%d ms",
                funName, className, methodName, duration));

        return result;
    }
}
@Aspect // 定义切面类
public class LoginCheckAspect {

    private final static String TAG = "TAG";

    // 1、应用中用到了哪些注解,放到当前的切入点进行处理(找到需要处理的切入点)
    // execution,以方法执行时作为切点,触发Aspect类
    // * *(..)) 可以处理ClickBehavior这个类所有的方法
    @Pointcut("execution(@com.kww.aopdmo.LoginCheck * *(..))")
    public void methodPointCut() {}

    // 2、对切入点如何处理
    @Around("methodPointCut()")
    public Object jointPotin(ProceedingJoinPoint joinPoint) throws Throwable {
        Context context = (Context) joinPoint.getThis();
        if (true) { // 从SharedPreferences中读取
            Log.e(TAG, "检测到已登录!");
            return joinPoint.proceed();
        } else {
            Log.e(TAG, "检测到未登录!");
            Toast.makeText(context, "请先登录!", Toast.LENGTH_SHORT).show();
            context.startActivity(new Intent(context, LoginActivity.class));
            return null; // 不再执行方法(切入点)
        }
    }
}



写在文末

纸上得来终觉浅,绝知此事要躬行。 《冬夜读书示子聿》-- 陆游

至此,AOP 的使用就说完了,各位看官食用愉快。


码字不易,如果本篇文章对您哪怕有一点点帮助,请不要吝啬您的点赞,我将持续带来更多优质文章。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值