Android 基于AOP监控之——AspectJ构建指南

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: ##如何使用## 使用AspectJ做AOP可以做一些非侵入的AOP监控操作,方便简洁,功能强大,而且对目标工程没有侵入性,可以做切面的操作:监听方法耗时、输出日志、控制初入参数、进行运行时修改等等操作。 在Eclipse中已经有AJDT插件集成了AspectJ编译器的使用和关键字的声明。但

如何使用

使用AspectJ做AOP可以做一些非侵入的AOP监控操作,方便简洁,功能强大,而且对目标工程没有侵入性,可以做切面的操作:监听方法耗时、输出日志、控制初入参数、进行运行时修改等等操作。

在Eclipse中已经有AJDT插件集成了AspectJ编译器的使用和关键字的声明。但是在Android Studio中没有这样的官方插件。因此,这里讲一下如何在Android Studio中使用AspectJ,来实现非侵入式的AOP监控。详细了解AspectJ的使用我在另一篇文章写了——Android基于AOP的非侵入式监控

AspectJ的使用核心就是它的编译器,它就做了一件事,将AspectJ的代码在编译期插入目标程序当中,运行时跟在其它地方没什么两样,因此要使用它最关键的就是使用它的编译器去编译代码ajc。ajc会构建目标程序与AspectJ代码的联系,在编译期将AspectJ代码插入被切出的PointCut中,已达到AOP的目的。
因此,无论在什么IDE上(如果使用命令行就可以直接使用ajc编译了),问题就是让IDE使用ajc作为编译器编译代码。

使用方法

1、插件:网上有人在github上提供了集成的插件gradle-android-aspectj-plugin,一开始我也是用的这个,但是在项目当中,无法兼容databinding,这个问题现在作者依然没有解决。

2、Gradle配置:通过在Gradle构建脚本中,定义任务来使得项目执行ajc编译,将AOP 的Module编织进入目标工程中,达到非侵入式AOP的目的。

下面就介绍一下第二种方法的具体的使用步骤。

Step

1、创建AS原工程

这里写图片描述

2、创建module(Android Library),然后添加AspectJ依赖至module中。

compile 'org.aspectj:aspectjrt:1.8.9'

3、编写build脚本,添加任务,使得IDE使用ajc作为编译器编译代码。

这里,分别对module、app的构建脚本添加一些任务,使得IDE使用ajc编译代码。

build.gradle(app):

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

apply plugin: 'com.android.application'

repositories {
    mavenCentral()
}

dependencies {
    compile project(':aspectjlibrary')
}

android {
    compileSdkVersion 21
    buildToolsVersion '22.0.1'

    buildTypes {
        debug {
            minifyEnabled false  // should disable proguard in debug builds
        }
    }
    defaultConfig {
        applicationId "com.example.lingyimly.try2"
        minSdkVersion 15
        targetSdkVersion 21

    }

    lintOptions {
        abortOnError true
    }

}


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.5",
                         "-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;
            }
        }
    }
}


build.gradle(module):

import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

apply plugin: 'com.android.library'
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0'
        classpath 'org.aspectj:aspectjtools:1.8.9'
        classpath 'org.aspectj:aspectjweaver:1.8.9'
    }
}


repositories {
    mavenCentral()
}
dependencies {
    compile 'org.aspectj:aspectjrt:1.8.9'
    compile 'com.android.support:appcompat-v7:22.2.1'
}
android {
    compileSdkVersion 22
    buildToolsVersion '23.0.1'

    lintOptions {
        abortOnError false
    }

}

android.libraryVariants.all { variant ->
    LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)
    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.5",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", plugin.project.android.bootClasspath.join(
                File.pathSeparator)]

        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler)

        def log = project.logger
        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:
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break;
            }
        }
    }
}

4、编写AspectJ切面程序代码

编写切面程序,做切面的操作:监听方法耗时、输出日志、控制初入参数、进行运行时修改

/**
 * 截获类名最后含有Activity、Layout的类的所有非static方法(static方法另外加一个static修饰的execution或者call即可:execution( static * *..Activity+.*(..))
 * 监听目标方法的执行时间
 */
@Aspect
public class TraceAspect {
  private static Object currentObject = null;
    //截获所有后缀为Activity或者Layout的类中所有方法的执行体(除了static,要监听static需要重新加一个static的execution 规则)
    //target、this是用于截获运行时类型,便于做一些入参、出参的修改,或者做其他操作
  private static final String POINTCUT_METHOD =
      "(execution(* *..Activity+.*(..)) ||execution(* *..Layout+.*(..))) && target(Object) && this(Object)";

    //截获所有后缀为Activity或者Layout的类中所有方法的调用(除了static,要监听static需要重新加一个static的execution 规则)
    //target、this是用于截获运行时类型,便于做一些入参、出参的修改,或者做其他操作
    private static final String POINTCUT_CALL = "(call(* *..Activity+.*(..)) || call(* *..Layout+.*(..))) && target(Object) && this(Object)";
  @Pointcut(POINTCUT_METHOD)
  public void methodAnnotated() {}
  @Pointcut(POINTCUT_CALL)
  public void methodCall(){}

    /**
     *  截获原方法,并替换
     * @param joinPoint
     * @return
     * @throws Throwable
     */
  @Around("methodAnnotated()")
  public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
    if (currentObject == null){
        currentObject = joinPoint.getTarget();
    }
      //初始化计时器
    final StopWatch stopWatch = new StopWatch();
      //开始监听
      stopWatch.start();
      //调用原方法的执行。
    Object result = joinPoint.proceed();
      //监听结束
    stopWatch.stop();
      //获取方法信息对象
      MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
      String className;
      //获取当前对象,通过反射获取类别详细信息
      className = joinPoint.getThis().getClass().getName();

      String methodName = methodSignature.getName();
      String msg =  buildLogMessage(methodName, stopWatch.getTotalTime(1));
    if (currentObject != null && currentObject.equals(joinPoint.getTarget())){
        DebugLog.log(new MethodMsg(className,msg,stopWatch.getTotalTime(1)));
    }else if(currentObject != null && !currentObject.equals(joinPoint.getTarget())){
        DebugLog.log(new MethodMsg(className, msg,stopWatch.getTotalTime(1)));
        Log.e(className,msg);
        currentObject = joinPoint.getTarget();
//        DebugLog.outPut(new Path());    //日志存储
//        DebugLog.ReadIn(new Path());    //日志读取
    }
    return result;
  }

    @After("methodCall()")
    public void onCallAfter(JoinPoint joinPoint) throws Throwable{
        Log.e("onCallAfter:", "class : "+joinPoint.getSignature().getDeclaringTypeName() + "method : " +((MethodSignature)joinPoint.getSignature()).getName());
    }
    /**
     * 在截获的目标方法调用之前执行该Advise
     * @param joinPoint
     * @throws Throwable
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Before("methodCall()")
    public void onCallBefore(JoinPoint joinPoint) throws Throwable{
        Log.e("onCallBefore:", "class : "+joinPoint.getSignature().getDeclaringTypeName() + "method : " +((MethodSignature)joinPoint.getSignature()).getName());
        Activity activity = null;
        //获取目标对象,截获运行时类型
        activity = ((Activity)joinPoint.getTarget());
        //插入自己的实现,控制目标对象的执行
        ChooseDialog dialog = new ChooseDialog(activity);
        dialog.show();

        //做其他的操作
        buildLogMessage("test",20);
    }
  /**
   * 创建一个日志信息
   *
   * @param methodName 方法名
   * @param methodDuration 执行时间
   * @return
   */
  private static String buildLogMessage(String methodName, double methodDuration) {
    StringBuilder message = new StringBuilder();
    message.append(methodName);
    message.append(" --> ");
    message.append("[");
    message.append(methodDuration);
    if (StopWatch.Accuracy == 1){
        message.append("ms");
    }else {
        message.append("mic");
    }
    message.append("]      \n");
    return message.toString();
  }

}


5 运行结果

启动Activity,点击Button进入另一个Activity

07-20 11:12:30.991 3536-3536/android10.org.viewgroupperformance E/onCallBefore:: class : org.android10.viewgroupperformance.activity.MainActivitymethod : setContentView
07-20 11:12:31.111 3536-3536/android10.org.viewgroupperformance E/onCallAfter:: class : org.android10.viewgroupperformance.activity.MainActivitymethod : setContentView
07-20 11:12:31.111 3536-3536/android10.org.viewgroupperformance E/onCallBefore:: class : org.android10.viewgroupperformance.activity.MainActivitymethod : mapGUI
07-20 11:12:31.131 3536-3536/android10.org.viewgroupperformance E/onCallBefore:: class : org.android10.viewgroupperformance.activity.MainActivitymethod : findViewById

07-20 11:13:34.391 3536-3536/android10.org.viewgroupperformance E/onCallBefore:: class : org.android10.viewgroupperformance.activity.MainActivitymethod : startActivity
07-20 11:13:34.441 3536-3536/android10.org.viewgroupperformance E/onCallAfter:: class : org.android10.viewgroupperformance.activity.MainActivitymethod : startActivity
07-20 11:13:34.501 3536-3536/android10.org.viewgroupperformance E/onCallBefore:: class : org.android10.viewgroupperformance.activity.RelativeLayoutTestActivitymethod : setContentView
07-20 11:13:34.531 3536-3536/android10.org.viewgroupperformance E/onCallAfter:: class : org.android10.viewgroupperformance.activity.RelativeLayoutTestActivitymethod : setContentView
07-20 11:13:34.531 3536-3536/android10.org.viewgroupperformance E/org.android10.viewgroupperformance.activity.RelativeLayoutTestActivity: onCreate --> [32.708ms]      

DEMO地址

AsptecjDemo

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
3月前
|
移动开发 监控 Android开发
Android & iOS 使用 ARMS 用户体验监控(RUM)的最佳实践
本文主要介绍了 ARMS 用户体验监控的基本功能特性,并介绍了在几种常见场景下的最佳实践。
473 14
|
3月前
|
安全 Android开发 iOS开发
Android vs. iOS:构建生态差异与技术较量的深度剖析###
本文深入探讨了Android与iOS两大移动操作系统在构建生态系统上的差异,揭示了它们各自的技术优势及面临的挑战。通过对比分析两者的开放性、用户体验、安全性及市场策略,本文旨在揭示这些差异如何塑造了当今智能手机市场的竞争格局,为开发者和用户提供决策参考。 ###
|
4月前
|
存储 Java Android开发
探索安卓应用开发:构建你的第一个"Hello World"应用
【9月更文挑战第24天】在本文中,我们将踏上一段激动人心的旅程,深入安卓应用开发的奥秘。通过一个简单而经典的“Hello World”项目,我们将解锁安卓应用开发的基础概念和步骤。无论你是编程新手还是希望扩展技能的老手,这篇文章都将为你提供一次实操体验。从搭建开发环境到运行你的应用,每一步都清晰易懂,确保你能顺利地迈出安卓开发的第一步。让我们开始吧,探索如何将一行简单的代码转变为一个功能齐全的安卓应用!
|
1月前
|
Java Android开发 开发者
探索安卓开发:构建你的第一个“Hello World”应用
在安卓开发的浩瀚海洋中,每个新手都渴望扬帆起航。本文将作为你的指南针,引领你通过创建一个简单的“Hello World”应用,迈出安卓开发的第一步。我们将一起搭建开发环境、了解基本概念,并编写第一行代码。就像印度圣雄甘地所说:“你必须成为你希望在世界上看到的改变。”让我们一起开始这段旅程,成为我们想要见到的开发者吧!
46 0
|
3月前
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
123 5
|
3月前
|
前端开发 JavaScript 测试技术
Android适合构建中大型项目的架构模式全面对比
Android适合构建中大型项目的架构模式全面对比
61 2
|
3月前
|
开发工具 Android开发 iOS开发
Android vs iOS:构建移动应用时的关键考量####
本文深入探讨了Android与iOS两大移动平台在开发环境、性能优化、用户体验设计及市场策略方面的差异性,旨在为开发者提供决策依据。通过对比分析,揭示两个平台各自的优势与挑战,帮助开发者根据项目需求做出更明智的选择。 ####
|
3月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
82 4
|
3月前
|
编解码 Android开发 UED
构建高效Android应用:从内存优化到用户体验
【10月更文挑战第11天】本文探讨了如何通过内存优化和用户体验改进来构建高效的Android应用。介绍了使用弱引用来减少内存占用、懒加载资源以降低启动时内存消耗、利用Kotlin协程进行异步处理以保持UI流畅,以及采用响应式设计适配不同屏幕尺寸等具体技术手段。
63 2
|
3月前
|
Java 编译器 Spring
Spring AOP 和 AspectJ 的区别
Spring AOP和AspectJ AOP都是面向切面编程(AOP)的实现,但它们在实现方式、灵活性、依赖性、性能和使用场景等方面存在显著区别。‌
126 2

热门文章

最新文章