Android基于AOP的非侵入式监控之——AspectJ实战

    • 一引言
    • 二什么是AspectJ
      • 1 它只是一个代码编译器
      • 2 它是用来做AOP编程的
      • 3为什么要用AspectJ
    • 三AspectJ原理与运用
      • 1 基本原理
      • 2 使用方式
        • 21 纯注解方式
        • 22 AspectJ语言
        • 23 结合自定义注解使用
    • 四AspectJ实战监听方法执行耗时打印并输出
    • 五一些比较常见的问题
    • 六推荐文章

一、引言

本博文的目的不是详细的介绍AspectJ的细节,而是最近项目用到了AspectJ,因此对其作了一些使用和重要概念上的总结。

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

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

  • OOP: 面向对象把所有的事物都当做对象看待,因此每一个对象都有自己的生命周期,都是一个封装的整体。每一个对象都有自己的一套垂直的系列方法和属性,使得我们使用对象的时候不需要太多的关系它的内部细节和实现过程,只需要关注输入和输出,这跟我们的思维方式非常相近,极大的降低了我们的编写代码成本(而不像C那样让人头痛!)。但在现实世界中,并不是所有问题都能完美得划分到模块中。举个最简单而又常见的例子:现在想为每个模块加上日志功能,要求模块运行时候能输出日志。在不知道AOP的情况下,一般的处理都是:先设计一个日志输出模块,这个模块提供日志输出API,比如Android中的Log类。然后,其他模块需要输出日志的时候调用Log类的几个函数,比如e(TAG,…),w(TAG,…),d(TAG,…),i(TAG,…)等。
  • AOP: 面向对象编程固然是开启另一个编程时代,但是久而久之也显露了它的缺点,最明显的一点就是它无法横向切割某一类方法、属性,当我们需要了解某一类方法、某一类属性的信息时,就必须要在每一个类的方法里面(即便他们是同样的方法,只因是不同的类所以不同)添加监控代码,在代码量庞大的情况下,这是一个不可取的方法。因此,AOP编产生了,基于AOP的编程可以让我们横向的切割某一类方法和属性(不需要关心他是什么类别!),我觉得AOP并不是与OOP对立的,而是为了弥补OOP的不足,因为有了AOP我们的调试和监控就变得简单清晰。

二、什么是AspectJ?

2.1 它只是一个代码编译器

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

2.2 它是用来做AOP编程的

AspectJ就是AOP,只不过是面向java的。AOP里面有一些重要基本的概念:

  • aspect(切面):实现了cross-cutting功能,是针对切面的模块。最常见的是logging模块、方法执行耗时模块,这样,程序按功能被分为好几层,如果按传统的继承的话,商业模型继承日志模块的话需要插入修改的地方太多,而通过创建一个切面就可以使用AOP来实现相同的功能了,我们可以针对不同的需求做出不同的切面。
  • jointpoint(连接点):连接点是切面插入应用程序的地方,该点能被方法调用,而且也会被抛出意外。连接点是应用程序提供给切面插入的地方,在插入地建立AspectJ程序与源程序的连接。

    下面列表上的是被AspectJ认为是joinpoint的:

  • advice(处理逻辑): advice是我们切面功能的实现,它是切点的真正执行的地方。比如像写日志到一个文件中,advice(包括:before、after、around等)在jointpoint处插入代码到应用程序中。我们来看一看原AspectJ程序和反编译过后的程序。看完下面的图我们就大概明白了AspectJ是如何达到监控源程序的信息了。


原Activity代码:



Advise:



反编译后的原代码:


  • pointcut(切点): pointcut可以控制你把哪些advice应用于jointpoint上去,通常你使用pointcuts通过正则表达式来把明显的名字和模式进行匹配应用。决定了那个jointpoint会获得通知。分为call、execution、target、this、within等关键字(具体含义见第四节)

2.3、为什么要用AspectJ?

1、非侵入式监控: 可以在不修监控目标的情况下监控其运行,截获某类方法,甚至可以修改其参数和运行轨迹!

2、学习成本低: 它就是Java,只要会Java就可以用它。

3、功能强大,可拓展性高: 它就是一个编译器+一个库,可以让开发者最大限度的发挥,实现形形色色的AOP程序!


三、AspectJ原理与运用

先放一块AspectJ代码,(这里使用的都是AspectJ较为常用的知识),接着在解释。


import android.annotation.TargetApi;
import android.app.Activity;
import android.graphics.Path;
import android.os.Build;

import org.android10.gintonic.internal.ChooseDialog;
import org.android10.gintonic.internal.DebugLog;
import org.android10.gintonic.internal.MethodMsg;
import org.android10.gintonic.internal.StopWatch;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

/**
 * 截获类名最后含有Activity、Layout的类的所有方法
 * 监听目标方法的执行时间
 */
@Aspect
public class TraceAspect {
  private static Object currentObject = null;
  //进行类似于正则表达式的匹配,被匹配到的方法都会被截获
  ////截获任何包中以类名以Activity、Layout结尾,并且该目标类和当前类是一个Object的对象的所有方法
  private static final String POINTCUT_METHOD =
      "(execution(* *..Activity+.*(..)) ||execution(* *..Layout+.*(..))) && target(Object) && this(Object)";
   //精确截获MyFrameLayou的onMeasure方法
    private static final String POINTCUT_CALL = "call(* org.android10.viewgroupperformance.component.MyFrameLayout.onMeasure(..))";

  private static final String POINTCUT_METHOD_MAINACTIVITY = "execution(* *..MainActivity+.onCreate(..))";

  //切点,ajc会将切点对应的Advise编织入目标程序当中
  @Pointcut(POINTCUT_METHOD)
  public void methodAnnotated() {}
  @Pointcut(POINTCUT_METHOD_MAINACTIVITY)
  public void methodAnootatedWith(){}

    /**
     * 在截获的目标方法调用之前执行该Advise
     * @param joinPoint
     * @throws Throwable
     */
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  @Before("methodAnootatedWith()")
  public void onCreateBefore(JoinPoint joinPoint) throws Throwable{
      Activity activity = null;
      //获取目标对象
      activity = ((Activity)joinPoint.getTarget());
      //插入自己的实现,控制目标对象的执行
      ChooseDialog dialog = new ChooseDialog(activity);
      dialog.show();

      //做其他的操作
      buildLogMessage("test",20);
  }
    /**
     * 在截获的目标方法调用返回之后(无论正常还是异常)执行该Advise
     * @param joinPoint
     * @throws Throwable
     */
 @After("methodAnootatedWith()")
  public void onCreateAfter(JoinPoint joinPoint) throws Throwable{
      Log.e("onCreateAfter:","onCreate is end .");

  }
    /**
     * 在截获的目标方法体开始执行时(刚进入该方法实体时)调用
     * @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();
    if (currentObject != null && currentObject.equals(joinPoint.getTarget())){
        DebugLog.log(new MethodMsg(className, buildLogMessage(methodName, stopWatch.getTotalTimeMicros()),stopWatch.getTotalTimeMicros()));
    }else if(currentObject != null && !currentObject.equals(joinPoint.getTarget())){
        DebugLog.log(new MethodMsg(className, buildLogMessage(methodName, stopWatch.getTotalTimeMicros()),stopWatch.getTotalTimeMicros()));
        currentObject = joinPoint.getTarget();
        DebugLog.outPut(new Path());    //日志存储
        DebugLog.ReadIn(new Path());    //日志读取
    }
    return result;
  }

  /**
   * 创建一个日志信息
   *
   * @param methodName 方法名
   * @param methodDuration 执行时间
   * @return
   */
  private static String buildLogMessage(String methodName, long 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("]      ");

    return message.toString();
  }

}

3.1 基本原理

在编译期对目标对象、方法做标记,对目标类、方法进行重构,将PointCut插入目标中,截获该目标的信息以及上下文环境,以达到非侵入代码监控的目的——注意,它只能获得对象的声明,如果对象的声明式接口,那么默认情况下(不使用this、target约束切点),获取的是声明类型,而不是具体运行时的类。

  1. 编写Aspect:声明Aspect、PointCut和Advise。
  2. ajc编织: AspectJ编译器在编译期间对所切点所在的目标类进行了重构,在编译层将AspectJ程序与目标程序进行双向关联,生成新的目标字节码,即将AspectJ的切点和其余辅助的信息类段插入目标方法和目标类中,同时也传回了目标类以及其实例引用。这样便能够在AspectJ程序里对目标程序进行监听甚至操控。
  3. execution: 顾名思义,它截获的是方法真正执行的代码区,Around方法块就是专门为它存在的。调用Around可以控制原方法的执行与否,可以选择执行也可以选择替换。
//截获任何包中以类名以Activity、Layout结尾,并且该目标类和当前类是一个Object的对象的所有方法
private static final String POINTCUT_METHOD =
      "(execution(* *..Activity+.*(..)) ||execution(* *..Layout+.*(..))) && target(Object) && this(Object)";
  //基于execution的切点
  @Pointcut(POINTCUT_METHOD)
  public void methodAnnotated() {}


4 . call: 同样,从名字可以看出,call截获的是方法的调用区,它并不截获代码真正的执行区域,它截获的是方法调用之前与调用之后(与before、after配合使用),在调用方法的前后插入JoinPoint和before、after通知。它截获的信息并没有execution那么多,它无法控制原来方法的执行与否,只是在方法调用前后插入切点,因此它比较适合做一些轻量的监控(方法调用耗时,方法的返回值等)。

 //精确截获MyFrameLayou的onMeasure方法
    private static final String POINTCUT_CALL = "call(* org.android10.viewgroupperformance.component.MyFrameLayout.onMeasure(..))";
    //基于call的切点
    @Pointcut(POINTCUT_METHOD_MAINACTIVITY)
  public void methodAnootatedWith(){}


5 . Around替代原理:目标方法体被Around方法替换,原方法重新生成,名为XXX_aroundBody(),如果要调用原方法需要在AspectJ程序的Around方法体内调用joinPoint.proceed()还原方法执行,是这样达到替换原方法的目的。达到这个目的需要双方互相引用,桥梁便是Aspect类,目标程序插入了Aspect类所在的包获取引用。AspectJ通过在目标类里面加入Closure(闭包)类,该类构造函数包含了目标类实例、目标方法参数、JoinPoint对象等信息,同时该类作为切点原方法的执行代理,该闭包通过Aspect类调用Around方法传入Aspect程序。这样便达到了关联的目的,便可以在Aspect程序中监控和修改目标程序。

/**
     * 在截获的目标方法体开始执行时(刚进入该方法实体时)调用
     * @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();
    if (currentObject != null && currentObject.equals(joinPoint.getTarget())){
        DebugLog.log(new MethodMsg(className, buildLogMessage(methodName, stopWatch.getTotalTimeMicros()),stopWatch.getTotalTimeMicros()));
    }else if(currentObject != null && !currentObject.equals(joinPoint.getTarget())){
        DebugLog.log(new MethodMsg(className, buildLogMessage(methodName, stopWatch.getTotalTimeMicros()),stopWatch.getTotalTimeMicros()));
        currentObject = joinPoint.getTarget();
        DebugLog.outPut(new Path());    //日志存储
        DebugLog.ReadIn(new Path());    //日志读取
    }
    return result;
  }


6 . Before与After: Before与After只是在方法被调用前和调用之后添加JoinPoint和通知方法(直接插入原程序方法体中),调用AspectJ程序定义的Advise方法,它并不替代原方法,是在方法call之前和之后做一个插入操作。After分为returnning和throwing两类,前者是在正常returning之后调用,后者是在throwing发生之后调用。默认的After是在finally处调用,因此它包含了前面的两种情况。

   /**
     * 在截获的目标方法调用之前执行该Advise
     * @param joinPoint
     * @throws Throwable
     */
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  @Before("methodAnootatedWith()")
  public void onCreateBefore(JoinPoint joinPoint) throws Throwable{
      Activity activity = null;
      //获取目标对象
      activity = ((Activity)joinPoint.getTarget());
      //插入自己的实现,控制目标对象的执行
      ChooseDialog dialog = new ChooseDialog(activity);
      dialog.show();

      //做其他的操作
      buildLogMessage("test",20);
  }
    /**
     * 在截获的目标方法调用返回之后(无论正常还是异常)执行该Advise
     * @param joinPoint
     * @throws Throwable
     */
 @After("methodAnootatedWith()")
  public void onCreateAfter(JoinPoint joinPoint) throws Throwable{
      Log.e("onCreateAfter:","onCreate is end .");

  }

7 . 重要关键字:

在其它关键字中,必须要注意的就是this、target的使用和区别,同时还有一个很重要的方法**Signature.getDeclaringType();**AspectJ是在编译期截获的对象信息,因此它获得的标签只是对象的声明(比如:接口、抽象类),而不是运行时具体的对象。如果想要获得运行时对象,就需要用this、target关键字

this  :用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
within:用于匹配指定类型内的方法执行;

更加详细的解说请参考深入理解Android之AOP,该博文对于AspectJ的其他详细概念、定义、细节示例解说的非常清楚,如果想要详细了解请务必要看。


3.2 使用方式

3.2.1 纯注解方式

上面贴的代码就是该方式,也是最普遍的方式,它不需要其他插件的支持(Eclipse中有AJDT可以支持AspectJ关键字声明,但Android Studio中没有改插件),使用Java的注解和ajc以及它的库就可以完成AOP编程,非常方便,而且可以在绝大部分支持Java的IDE中使用。缺点就是对于注释部分的匹配没有检错功能。


/**
 * Created by lingyi.mly on 2016/5/21.
 */
@Aspect
public class TraceAspect3 {
    private static volatile Object currentObject = null;
    private ExecutorService ThreadPool = Executors.newFixedThreadPool(10);
    private static final String POINTCUT_METHOD =
            "call(* *.*(..))&&target(Object) &&!within(*.TimeMonitorFragment)";
    @Pointcut(POINTCUT_METHOD)
    public void methodAnnotated() {
    }

    StopWatch stopWatch;
    MethodSignature methodSignature;
    String methodName;
    String className;

    @Before("methodAnnotated()")
    public void beforeInvoked(final JoinPoint joinPoint) {
        className = "call target: " + joinPoint.getTarget().getClass().getName();
        methodSignature = (MethodSignature) joinPoint.getSignature();
        methodName = methodSignature.getName();
        stopWatch = new StopWatch();
        stopWatch.start();
    }

    @After("methodAnnotated()")
    public void afterInvoked(final JoinPoint joinPoint) {
        stopWatch.stop();
        double methodDuration = stopWatch.getTotalTime(StopWatch.Accuracy);
        DebugLog.log(new MethodMsg(className, methodName, methodDuration, StopWatch.Accuracy));
    }
}

3.2.2 AspectJ语言

在Eclipse中使用AJDT插件,可以识别AspectJ的语法。这样编写起来相对于注解要方便许多,还提供检错功能,比较强大。不过不是所有的IDE都支持,比如Android Studio目前就没有(我哭了好久)。

package main;
import java.util.HashMap;
import java.util.Map;
/**
 * 只有call才能区分this 与target 在与的情况下两者不共存,在交的情况下共存。
 * execution匹配this 与 target时无论是 与 还是 交集 都是同一个对象
 * @author lingyi.mly
 *
 */

public aspect Aspect{
    static int count = 0;
    pointcut targetTest() : call(* main.*.*(..)) &&( target(Object) );
    pointcut thisTest( ) : execution(* main.*.*(..)) && (target(Object) ||this(Object));
    Object around() : thisTest() {
        if (thisJoinPoint.getThis() != null) {
            System.out.println(thisJoinPoint.getThis().getClass().getName()  +  "   " + thisJoinPoint.getSourceLocation());
        }else if (thisJoinPoint.getTarget() != null) {
            System.out.println(thisJoinPoint.getTarget().getClass().getName()  +  "   " + thisJoinPoint.getSourceLocation());
        }
        return null;
    }
    before() : targetTest() {
        if (thisJoinPoint.getThis() != null) {
            System.out.println("this:  "+thisJoinPoint.getThis().getClass().getName()  +  "   " + thisJoinPoint.getSourceLocation());
        }else if (thisJoinPoint.getTarget() != null) {
            System.out.println("target:  "+thisJoinPoint.getTarget().getClass().getName()  +  "   " + thisJoinPoint.getSourceLocation());
        }
    }

    private static Map<String, Integer> threadMap = new HashMap<String,Integer>();
}

3.2.3 结合自定义注解使用

这个是混合用法,可以在execution、call中使用注解,然后该注解标注在目标方法上就可以实现关联,并且截获。这样做的好处实在想不到,最多就是可以精确定位到某一个方法(那使用绝对路径匹配不也可以?)。而且还侵入了源码。实在是不推荐,不过我在网上看到有人这么用了,所以也贴上来了。如果哪位高手知道这样做的精髓,请一定指教。下面贴一下它的用法实现。

自定义注解及被标记的方法:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 表明被注释的方法将被跟踪(仅在Debug模式下)并且将会与Aspect程序中截获该注释的Advise关联,调用该切点
 * 的Advise
 */
@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD })
public @interface DebugTrace {}

/**
 * 被注解的类
 */
public class MyFrameLayout extends FrameLayout {
  //........
  //被注解的方法
  @DebugTrace
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  }
  @DebugTrace
  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
  }
}

切面:

package org.android10.gintonic.aspect;

/**
 * 跟踪被DebugTrace注解标记的方法和构造函数
 */
@Aspect
public class TraceAspect {
  //跟踪DebugTrace注解
  private static final String POINTCUT_METHOD =
      "execution(@org.android10.gintonic.annotation.DebugTrace * *(..))";

  @Pointcut(POINTCUT_METHOD)
  public void methodAnnotatedWithDebugTrace() {}

  @Around("methodAnnotatedWithDebugTrace() || constructorAnnotatedDebugTrace()")
  public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
    // Do SomeThing
    stopWatch.start();
    Object result = joinPoint.proceed();
    stopWatch.stop();
    // Do SomeThing
    return result;
  }
  // ........省略
}

四、AspectJ实战——监听方法执行耗时,打印并输出

源程序代码:Android-AOPExample-master

关键代码:

private static final String POINTCUT_METHOD =
      "(execution(* *..Activity+.*(..)) ||execution(* *..Layout+.*(..))) && target(Object) && this(Object)";
  // ...........
  @Pointcut(POINTCUT_METHOD)
  public void methodAnnotated() {}
  // .........

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

监听方法执行时间:

TimeMonitor:: org.android10.viewgroupperformance.activity.RelativeLayoutTestActivity     onCreate --> [8.636ms]
org.android10.viewgroupperformance.activity.MainActivity     openActivity --> [6.561ms]
org.android10.viewgroupperformance.activity.MainActivity     mapGUI --> [0.061ms]      

五、一些比较常见的问题

(1)问题:AspectJ中Signature提供的getDeclareType返回的是声明类型,无法获取运行时类型,因此无法准确获取接口运行时类别。

方案:使用target关键字约束pointCut,获取目标对象,通过反射获取其运行时类别。

(2)问题:使用target关键字约束pointcut获取目标对象Object之后,无法获取静态方法(不属于对象)

方案:单独将静态方法提出来,再与前面的target关键字约束的集合取并集。

(3)问题:使用Before、After通知,测试方法耗时的精确度误差较大

方案:改用execution+around。两点,第一:由于Before、After是在原方法调用前后插入通知(会影响本来所在方法快的执行速率);第二:同时Before、After两个操作无法保证是原子操作,多线程情况下会有误差。因此该用execution关键字,截获方法体的真正执行处,使用Around通知,替代原方法(原方法被更名,但结构不变),在Around通知体内调用原方法计时,这样能够真正还原方法执行耗时;


六、推荐文章

深入理解Android之AOP

官方英文文档

跟我学习AspectJ系列

时间: 2024-10-14 06:06:29

Android基于AOP的非侵入式监控之——AspectJ实战的相关文章

Android 基于AOP监控之——AspectJ使用指南

如何使用 使用方法 Step 1创建AS原工程 2创建moduleAndroid Library然后添加AspectJ依赖必须添加至module中添加至APP工程中ajc编译器是不会重构目标代码的 3编写build脚本添加任务使得IDE使用ajc作为编译器编译代码 4编写AspectJ切面程序代码 运行结果 DEMO地址 如何使用 在Eclipse中已经有AJDT插件集成了AspectJ编译器的使用和关键字的声明.但是在Android Studio中没有这样的官方插件.因此,这里讲一下如何在An

BlockCanary 一个轻量的,非侵入式的性能监控组件(阿里)

开发者博客: BlockCanary — 轻松找出Android App界面卡顿元凶 开源代码:moduth/blockcanary BlockCanary对主线程操作进行了完全透明的监控,并能输出有效的信息,帮助开发分析.定位到问题所在,迅速优化应用.其特点有: 非侵入式,简单的两行就打开监控,不需要到处打点,破坏代码优雅性. 精准,输出的信息可以帮助定位到问题所在(精确到行),不需要像Logcat一样,慢慢去找. 目前包括了核心监控输出文件,以及UI显示卡顿信息功能.仅支持Android端.

支付宝开源非侵入式 Android 自动化测试工具 Soloπ

Soloπ(SoloPi)是支付宝开源的一个无线化.非侵入式的Android自动化测试工具,公测版拥有录制回放.性能测试.一机多控三项主要功能,能为测试开发人员节省宝贵时间. 本文是SoloPi团队关于项目的深度解读, 作者:乔瑞凯,蚂蚁金服高级无线开发工程师 前言 近年来,随着移动互联网的蓬勃发展,移动测试技术也取得了长足的进步,从早期基于测试脚本的单机自动化,到录制回放.图像识别.云测平台等测试技术贴合实际业务需求深度应用和创新,测试效率从而一次又一次被提升. 本文主要介绍支付宝在移动端上实

MVC的验证(模型注解和非侵入式脚本的结合使用)

@HtmlHrlper方式创建的标签,会自动生成一些属性,其中一些属性就是关于验证 如图示例: 模型注解 通过模型注解后,MVC的验证,包括前台客户端,后台服务器的验证,MVC统统都做了包含,即使用户在客户端禁用Javascript,服务器也会将非法操作进行验证,当前前提是针对Model实体标识了注解的情况. 要能够正常进行非空等合法性验证必须做如下步骤(前提条件): 1.必须在实体的每个类型上加上Required特性,但是数字型的属性默认已经加上了. 2.必须在视图上导入如下脚本: <scri

Spring 侵入式和非侵入式

1.非侵入式的技术体现 允许在应用系统中自由选择和组装Spring框架的各个功能模块,并且不强制要求应用系统的类必须从Spring框架的系统API的某个类来继承或者实现某个接口. 2.如何实现非侵入式的设计目标的 1)应用反射机制,通过动态调用的方式来提供各方面的功能,建立核心组间BeanFactory 2)配合使用Spring框架中的BeanWrapper和BeanFactory组件类最终达到对象的实例创建和属性注入 3)优点:允许所开发出来的应用系统能够在不用的环境中自由移植,不需要修改应用

eclipse插件安装:非侵入式方法

非侵入式安装插件方法(links安装方法) 既然有侵入式的安装,言下之意,还有个“非侵入式安装”,也有人成为links安装方法,下面看究竟是如何安装的:). 非侵入式安装也分两种,一种是绝对路径安装方法,一种是相对路径的安装方法.首先看绝对路径的安装的方法. 在上面安装过程中,我们将中文语言包NLpack1-eclipse-SDK-3.2.1-win32.zip插件解压缩到一个地方假设为F:\myplugins目录文件夹的目录结构如下: NLpack1-eclipse-SDK-3.2.1-win

侵入式和非侵入式的区别

非侵入式设计 一个客户端的代码可能包含框架功能和客户端自己的功能. 侵入式设计,就是设计者将框架功能“推”给客户端,而非侵入式设计,则是设计者将客户端的功能“拿”到框架中用. 侵入式设计有时候表现为客户端需要继承框架中的类,而非侵入式设计则表现为客户端实现框架提供的接口. 侵入式设计带来的最大缺陷是,当你决定重构你的代码时,发现之前写过的代码只能扔掉.而非侵入式设计则不然,之前写过的代码仍有价值. struts1的设计是侵入式的: public class loginAction extends

Spring aop——前置增强和后置增强 使用注解Aspect和非侵入式配置

AspectJ是一个面向切面的框架,它扩展了java语言,定义了AOP语法,能够在编译期提供代码的织入,所以它有一个专门的编译器用来生成遵守字节码字节编码规范的Class文件 确保使用jdk为5.0以上版本. 01.使用注解标注增强(AspectJ)  :取代了配置文件中的aop:pointcut节点的配置 添加jar和log4j的配置文件 aspectj-1.8.7.jar aspectjweaver.jar 添加头文件: xmlns:aop="http://www.springframewo

使用AOP思想无侵入式申请权限,解决组件化中权限问题(一)

首先介绍AspectJx使用 https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx 在根项目的build.gradle文件中添加上依赖 dependencies { classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4' } 在app项目的build.gradle里应用插件 apply plugin: 'android-aspect