Android中的AOP编程之AspectJ实战实现数据埋点

文章背景

最近在给某某银行做项目的时,涉及到了数据埋点,性能监控等问题,那我们起先想到的有两种方案,方案之一就是借助第三方,比如友盟、Bugly等,由于项目是部署在银行的网络框架之内的,所以该方案不可行。另外一种方案是就是给每一个方法里面数据打点,然后写入SD卡,定时上报给自己的服务器,服务器来进行统计分析,这种方案看上去似乎可行,但有弊端,不仅会给程序员增加巨大工作量、而且最致命的是会严重拖累整个APP的性能。好多都应无奈之举放弃了该需求,但数据埋点实现用户行为的收集分析和性能监控对于技术部和运营部来说是一件非常有价值的事情,所以作为程序的我必应寻找解决方案,庆幸的是我们除了OOP编程思想外,还有一种编程思想就是AOP编程,这种编程思想能解决此类问题。

文章目标

实现用户行为采集
实现方法性能监控
探讨AOP编程实战

看图简单解读Android的AOP实战

aop0

aop1

看到没有,在LinearLayoutTestActivity中除了加载布局的操作外,我并没有干其他的什么,但当我点击菜单跳转到该Activity时,onCreate的方法和参数被打印出来,甚至LinearLayoutTestActivity类信息也被打印出来了,干这个事情的是TraceAspect这个类。到这里上面所说的用户的行为跟踪就轻而易举得以实现,那么下面我们开始来了解一下这种技术。

什么是AOP

在讲AOP之前我们首先回顾一下我们经常使用OOP思想,即『面向对象编程』,它提倡的是将功能模块化,对象化,面向对象把所有的事物都当做对象看待,因此每一个对象都有自己的生命周期,都是一个封装的整体。每一个对象都有自己的一套垂直的系列方法和属性,使得我们使用对象的时候不需要太多的关系它的内部细节和实现过程,只需要关注输入和输出,这跟我们的思维方式非常相近,极大的降低了我们的编写代码成本(而不像C那样让人头痛!)。但在现实世界中,并不是所有问题都能完美得划分到模块中。举个最简单而又常见的例子:现在想为每个模块加上日志功能,要求模块运行时候能输出日志。在不知道AOP的情况下,一般的处理都是:先设计一个日志输出模块,这个模块提供日志输出API,比如Android中的Log类。然后,其他模块需要输出日志的时候调用Log类的几个函数,比如e(TAG,…),w(TAG,…),d(TAG,…),i(TAG,…)等。而AOP的思想,则不太一样,它提倡的是针对同一类问题的统一处理,基于AOP的编程可以让我们横向的切割某一类方法和属性(不需要关心他是什么类别!),我觉得AOP并不是与OOP对立的,而是为了弥补OOP的不足,因为有了AOP我们的调试和监控就变得简单清晰,所以很多时候,可能会混合多种编程思想
**面向切面编程(AOP,Aspect-oriented programming)**需要把程序逻辑分解成『关注点』(concerns,功能的内聚区域)。这意味着,在 AOP 中,我们不需要显式的修改就可以向代码中添加可执行的代码块。这种编程范式假定『横切关注点』(cross-cutting concerns,多处代码中需要的逻辑,但没有一个单独的类来实现)应该只被实现一次,且能够多次注入到需要该逻辑的地方。

代码注入是 AOP 中的重要部分:它在处理上述提及的横切整个应用的『关注点』时很有用,例如日志或者性能监控。这种方式,并不如你所想的应用甚少,相反的,每个程序员都可以有使用这种注入代码能力的场景,这样可以避免很多痛苦和无奈。

AOP 是一种已经存在了很多年的编程范式。我发现把它应用到 Android 开发中也很有用。经过一番调研后,我认为我们用它可以获得很多好处和有用的东西。

AspectJ是什么

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

为什么用 AspectJ?

功能强大:它就是一个编译器+一个库,可以让开发者最大限度的发挥,实现形形色色的AOP程序!
非侵入式监控: 可以在不修监控目标的情况下监控其运行,截获某类方法,甚至可以修改其参数和运行轨迹!
支持编译期和加载时代码注入,不影响性能。
易用易学:它就是Java,只要会Java就可以用它。
目前最好

在Android项目中使用AspectJ
使用方法有两种:
1、 插件的方式:网上有人在github上提供了集成的插件gradle-android-aspectj-plugin。这种方式配置简单方便,但经测试无法兼容databinding框架,这个问题现在作者依然没有解决,希望作者能够快速解决。

2、Gradle配置的方式:配置有点麻烦,不过国外一个大牛在build文件中添加了一些脚本,虽然有点难懂,但可以在AS中使用。文章出处:https://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/

Step

1、创建一个AS原工程

aop2

2、再创建一个module(Android Library)
aop3

3、在gintonic中添加AspectJ依赖,同时编写build脚本,添加任务,使得IDE使用ajc作为编译器编译代码,然后把该Module添加至主工程Module中。

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

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:2.1.0'
    classpath 'org.aspectj:aspectjtools:1.8.1'
  }
}

apply plugin: 'com.android.library'

repositories {
  mavenCentral()
}

dependencies {
  compile 'org.aspectj:aspectjrt:1.8.1'
}

android {
  compileSdkVersion 21
  buildToolsVersion '21.1.2'

  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、在主build.gradle(Module:app)中添加也要添加AspectJ依赖,同时编写build脚本,添加任务,目的就是为了建立两者的通信,使得IDE使用ajc编译代码。

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.1'
    }
}
repositories {
    mavenCentral()
}

android {


    compileSdkVersion 21
    buildToolsVersion '21.1.2'

    defaultConfig {
        applicationId 'com.example.myaspectjapplication'
        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;
            }
        }
    }
}
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:25.3.1'
    //compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    compile project(':gintonic')
    compile 'org.aspectj:aspectjrt:1.8.1'
}

5、在Module(gintonic)中新建一个名为"TraceAspect"类

@Aspect
public class TraceAspect {

  //ydc start
  private static final String TAG = "ydc";
  @Before("execution(* android.app.Activity.on**(..))")
  public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
    String key = joinPoint.getSignature().toString();
    Log.d(TAG, "onActivityMethodBefore: " + key+"\n"+joinPoint.getThis());
  }
 }

上面的代码片段中有两处地方值得注意,一个是把这个类注解为@Aspect,另一个是给方法的的注解并加上了类似正则表达式的过滤条件,我们先按照我的步骤走,后面会一一讲解。

6、LinearLayoutTestActivity类

public class LinearLayoutTestActivity extends Activity {

  private LinearLayout myLinearLayout;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_linear_layout_test);

    myLinearLayout = (LinearLayout) findViewById(R.id.linearLayoutOne);
    myLinearLayout.invalidate();
  }
}

然后运行我们的程序看日志打印效果

aop8

上面的代码片段中有两处地方值得注意,一个是把这个类注解为@Aspect,另一个是给方法的的注解并加上了类似正则表达式的过滤条件,我们先按照我的步骤走,后面会一一讲解。

根据图片我们会惊奇的发现LinearLayoutTestActivity中的onCreate(Bundle savedInstanceState)方法被TraceAspect类赤裸裸的监控了,不仅截取到了LinearLayoutTestActivity类信息和方法及方法参数。

那这到底是怎么回事呢?我们可以使用反编译我的apk看一下相关的代码

aop7

我们可以发现,在onCreate执行之前,插入了一些AspectJ的代码,并且调用了TraceAspect中的 onActivityMethodBefore(JoinPoint joinPoint)方法。这个就是AspectJ的主要功能,抛开AOP的思想来说,我们想做的,实际上就是『在不侵入原有代码的基础上,增加新的代码』。

监控Activity的下其它被调用的方法

aop9

看到没有我们仅仅在TraceAspect类中编写一个方法就可以监控RelativeLayoutTestActivity中被用户点击的方法,这样就可以轻轻松松采集用户行为

@Around("execution(* com.example.myaspectjapplication.activity.RelativeLayoutTestActivity.testAOP())")
  public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    String key = proceedingJoinPoint.getSignature().toString();
    Log.d(TAG, "onActivityMethodAroundFirst: " + key);
    proceedingJoinPoint.proceed();
    Log.d(TAG, "onActivityMethodAroundSecond: " + key);
  }

我们还是照样看来看一下反编译的代码
这是在RelativeLayoutTestActivity类中调用testAOP()我们的源码:


public class RelativeLayoutTestActivity extends Activity {

  Button btn_test,btn_test2;
  //public static String A="88888";
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_relative_layout_test);
    btn_test=(Button)findViewById(R.id.btn_test);
    btn_test.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        testAOP();
      }
    });
    btn_test2=(Button)findViewById(R.id.btn_test2);
    btn_test2.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
          mytestDebug();
      }
    });

  }

  private  void testAOP(){

    int cunt=0;
    for ( int i=0;i<1000;i++){
      cunt++;

    }
    //Log.d("ydc","cunt:"+cunt+"");
  }

  private void method4Call() {
    //System.out.println("in method method4Call");
  }

  @DebugTrace
  private void  mytestDebug(){

  }
}

下面是反编译的代码,读者只要关注testAOP()方法即可

package com.example.myaspectjapplication.activity;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import org.android10.gintonic.annotation.DebugTrace;
import org.android10.gintonic.aspect.TraceAspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.StaticPart;
import org.aspectj.runtime.internal.AroundClosure;
import org.aspectj.runtime.reflect.Factory;

public class RelativeLayoutTestActivity extends Activity
{
  private static final JoinPoint.StaticPart ajc$tjp_0;
  private static final JoinPoint.StaticPart ajc$tjp_1;
  private static final JoinPoint.StaticPart ajc$tjp_2;
  Button btn_test;
  Button btn_test2;

  static
  {
    ajc$preClinit();
  }

  private static void ajc$preClinit()
  {
    Factory localFactory = new Factory("RelativeLayoutTestActivity.java", RelativeLayoutTestActivity.class);
    ajc$tjp_0 = localFactory.makeSJP("method-execution", localFactory.makeMethodSig("4", "onCreate", "com.example.myaspectjapplication.activity.RelativeLayoutTestActivity", "android.os.Bundle", "savedInstanceState", "", "void"), 27);
    ajc$tjp_1 = localFactory.makeSJP("method-execution", localFactory.makeMethodSig("2", "testAOP", "com.example.myaspectjapplication.activity.RelativeLayoutTestActivity", "", "", "", "void"), 48);
    ajc$tjp_2 = localFactory.makeSJP("method-execution", localFactory.makeMethodSig("2", "mytestDebug", "com.example.myaspectjapplication.activity.RelativeLayoutTestActivity", "", "", "", "void"), 63);
  }

  private void method4Call()
  {
  }

  @DebugTrace
  private void mytestDebug()
  {
    JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_2, this, this);
    TraceAspect.aspectOf().weaveJoinPoint(new RelativeLayoutTestActivity.AjcClosure3(new Object[] { this, localJoinPoint }).linkClosureAndJoinPoint(69648));
  }

  static final void mytestDebug_aroundBody2(RelativeLayoutTestActivity paramRelativeLayoutTestActivity, JoinPoint paramJoinPoint)
  {
  }

  private void testAOP()
  {
    JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_1, this, this);
    TraceAspect.aspectOf().onActivityMethodAround(new RelativeLayoutTestActivity.AjcClosure1(new Object[] { this, localJoinPoint }).linkClosureAndJoinPoint(69648));
  }

  static final void testAOP_aroundBody0(RelativeLayoutTestActivity paramRelativeLayoutTestActivity, JoinPoint paramJoinPoint)
  {
    int i = 0;
    for (int j = 0; j < 1000; j++)
      i++;
  }

  protected void onCreate(Bundle paramBundle)
  {
    JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_0, this, this, paramBundle);
    TraceAspect.aspectOf().onActivityMethodBefore(localJoinPoint);
    super.onCreate(paramBundle);
    setContentView(2130903043);
    this.btn_test = ((Button)findViewById(2131230727));
    this.btn_test.setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramAnonymousView)
      {
        RelativeLayoutTestActivity.this.testAOP();
      }
    });
    this.btn_test2 = ((Button)findViewById(2131230728));
    this.btn_test2.setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramAnonymousView)
      {
        RelativeLayoutTestActivity.this.mytestDebug();
      }
    });
  }
}

我们不难发现我们的代码轻松被AspectJ重构了,而且这种重构是在不修改原有代码的情况下无缝的被插入。

Fragment的中的方法监控
上面我已经演示过Activity中的方法强势插入,在Fragment中依然可行

@Before("execution(* android.app.Fragment.on**(..))")
  public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
    String key = joinPoint.getSignature().toString();
    Log.d(TAG, "onActivityMethodBefore: " + key+"\n"+joinPoint.getThis());
  }

aop14

AspectJ原理剖析

1、Join Points(连接点)
Join Points,简称JPoints,是AspectJ的核心思想之一,它就像一把刀,把程序的整个执行过程切成了一段段不同的部分。例如,构造方法调用、调用方法、方法执行、异常等等,这些都是Join Points,实际上,也就是你想把新的代码插在程序的哪个地方,是插在构造方法中,还是插在某个方法调用前,或者是插在某个方法中,这个地方就是Join Points,当然,不是所有地方都能给你插的,只有能插的地方,才叫Join Points。

2、Pointcuts(切入点)
告诉代码注入工具,在何处注入一段特定代码的表达式。例如,在哪些 joint points 应用一个特定的 Advice。切入点可以选择唯一一个,比如执行某一个方法,也可以有多个选择,可简单理解为带条件的Join Points,作为我们需要的代码切入点。

3、Advice(通知)
如何注入到我的class文件中的代码。典型的 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。 上面的例子中用的就是最简单的Advice——Before。

4、Aspect(切面): Pointcut 和 Advice 的组合看做切面。例如,我们在应用中通过定义一个 pointcut 和给定恰当的advice,添加一个日志切面。

5、Weaving(织入): 注入代码(advices)到目标位置(joint points)的过程。

AspectJ之切点语法解析

拿上面的代码片段说明

 private static final String TAG = "ydc";
  @Before("execution(* android.app.Activity.on**(..))")
  public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
    String key = joinPoint.getSignature().toString();
    Log.d(TAG, "onActivityMethodBefore: " + key+"\n"+joinPoint.getThis());
  }

拆分说明
1、@Before:Advice,也就是具体的插入点
2、execution:处理Join Point的类型,例如call、execution
3、(* android.app.Activity.on**(…)):这个是最重要的表达式,第一个『』表示返回值,『』表示返回值为任意类型,后面这个就是典型的包名路径,其中可以包含『』来进行通配,几个『』没区别。同时,这里可以通过『&&、||、!』来进行条件组合。()代表这个方法的参数,你可以指定类型,例如android.os.Bundle,或者(…)这样来代表任意类型、任意个数的参数。
4、public void onActivityMethodBefore:实际切入的代码。

Before和After的使用方法
这两个Advice应该是使用的最多的,所以,我们先来看下这两个Advice的实例,首先看下Before和After。

@Before("execution(* android.app.Activity.on*(android.os.Bundle))")
  public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
    String key = joinPoint.getSignature().toString();
    Log.d(TAG, "onActivityMethodBefore: " + key);
  }

  @After("execution(* android.app.Activity.on*(android.os.Bundle))")
  public void onActivityMethodAfter(JoinPoint joinPoint) throws Throwable {
    String key = joinPoint.getSignature().toString();
    Log.d(TAG, "onActivityMethodAfter: " + key);
  }

反编译之后在原始代码的基础上,增加了Before和After的代码,Log也能被正确的插入并打印出来。

aop15

Around的使用方法
Before和After其实还是很好理解的,也就是在Pointcuts之前和之后,插入代码,那么Around呢,从字面含义上来讲,也就是在方法前后各插入代码,是的,他包含了Before和After的全部功能,代码如下:

  //Around的用法
  @Around("execution(* com.example.myaspectjapplication.activity.RelativeLayoutTestActivity.testAOP())")
  public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    String key = proceedingJoinPoint.getSignature().toString();
    Log.d(TAG, "onActivityMethodAroundFirst: " + key);
    proceedingJoinPoint.proceed();
    Log.d(TAG, "onActivityMethodAroundSecond: " + key);
  }

其中,proceedingJoinPoint.proceed()代表执行原始的方法,在这之前、之后,都可以进行各种逻辑处理。

原始代码:


/**
 *
 */
public class RelativeLayoutTestActivity extends Activity {
  Button btn_test,btn_test2;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_relative_layout_test);
    btn_test=(Button)findViewById(R.id.btn_test);
    btn_test.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        testAOP();
      }
    });
    btn_test2=(Button)findViewById(R.id.btn_test2);
    btn_test2.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
          mytestDebug();
      }
    });

  }

  private  void testAOP(){

    int cunt=0;
    for ( int i=0;i<1000;i++){
      cunt++;

    }
    //Log.d("ydc","cunt:"+cunt+"");
  }

  private void method4Call() {
    //System.out.println("in method method4Call");
  }

  @DebugTrace
  private void  mytestDebug(){

  }
}

执行效果:
aop16

反编译后的代码

package com.example.myaspectjapplication.activity;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import org.android10.gintonic.annotation.DebugTrace;
import org.android10.gintonic.aspect.TraceAspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.StaticPart;
import org.aspectj.runtime.internal.AroundClosure;
import org.aspectj.runtime.reflect.Factory;

public class RelativeLayoutTestActivity extends Activity
{
  private static final JoinPoint.StaticPart ajc$tjp_0;
  private static final JoinPoint.StaticPart ajc$tjp_1;
  Button btn_test;
  Button btn_test2;

  static
  {
    ajc$preClinit();
  }

  private static void ajc$preClinit()
  {
    Factory localFactory = new Factory("RelativeLayoutTestActivity.java", RelativeLayoutTestActivity.class);
    ajc$tjp_0 = localFactory.makeSJP("method-execution", localFactory.makeMethodSig("2", "testAOP", "com.example.myaspectjapplication.activity.RelativeLayoutTestActivity", "", "", "", "void"), 46);
    ajc$tjp_1 = localFactory.makeSJP("method-execution", localFactory.makeMethodSig("2", "mytestDebug", "com.example.myaspectjapplication.activity.RelativeLayoutTestActivity", "", "", "", "void"), 61);
  }

  private void method4Call()
  {
  }

  @DebugTrace
  private void mytestDebug()
  {
    JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_1, this, this);
    TraceAspect.aspectOf().weaveJoinPoint(new RelativeLayoutTestActivity.AjcClosure3(new Object[] { this, localJoinPoint }).linkClosureAndJoinPoint(69648));
  }

  static final void mytestDebug_aroundBody2(RelativeLayoutTestActivity paramRelativeLayoutTestActivity, JoinPoint paramJoinPoint)
  {
  }

  private void testAOP()
  {
    JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_0, this, this);
    TraceAspect.aspectOf().onActivityMethodAround(new RelativeLayoutTestActivity.AjcClosure1(new Object[] { this, localJoinPoint }).linkClosureAndJoinPoint(69648));
  }

  static final void testAOP_aroundBody0(RelativeLayoutTestActivity paramRelativeLayoutTestActivity, JoinPoint paramJoinPoint)
  {
    int i = 0;
    for (int j = 0; j < 1000; j++)
      i++;
  }

  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130903043);
    this.btn_test = ((Button)findViewById(2131230727));
    this.btn_test.setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramAnonymousView)
      {
        RelativeLayoutTestActivity.this.testAOP();
      }
    });
    this.btn_test2 = ((Button)findViewById(2131230728));
    this.btn_test2.setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramAnonymousView)
      {
        RelativeLayoutTestActivity.this.mytestDebug();
      }
    });
  }
}

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

call和execution
AspectJ的切入点表达式中,我们前面都是使用的execution,实际上,还有一种类型——call,那么这两种语法有什么区别呢,我们来试验下就知道了。

被切代码依然很简单:


public class RelativeLayoutTestActivity extends Activity {
  Button btn_test,btn_test2;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_relative_layout_test);
    btn_test=(Button)findViewById(R.id.btn_test);
    btn_test.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        testAOP();
      }
    });
    btn_test2=(Button)findViewById(R.id.btn_test2);
    btn_test2.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {

      }
    });

  }

  private  void testAOP(){

    int cunt=0;
    for ( int i=0;i<1000;i++){
      cunt++;

    }
    //Log.d("ydc","cunt:"+cunt+"");
  }
}

先来看execution,代码如下:

//execution用法
  @Before("execution(* com.example.myaspectjapplication.activity.RelativeLayoutTestActivity.testAOP(..))")
  public void methodAOPTest(JoinPoint joinPoint) throws Throwable {
    String key = joinPoint.getSignature().toString();
    Log.d(TAG, "methodAOPTest: " + key);
  }

编译之后的代码如下所示:

package com.example.myaspectjapplication.activity;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import org.android10.gintonic.aspect.TraceAspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.StaticPart;
import org.aspectj.runtime.reflect.Factory;

public class RelativeLayoutTestActivity extends Activity
{
  private static final JoinPoint.StaticPart ajc$tjp_0;
  Button btn_test;
  Button btn_test2;

  static
  {
    ajc$preClinit();
  }

  private static void ajc$preClinit()
  {
    Factory localFactory = new Factory("RelativeLayoutTestActivity.java", RelativeLayoutTestActivity.class);
    ajc$tjp_0 = localFactory.makeSJP("method-execution", localFactory.makeMethodSig("2", "testAOP", "com.example.myaspectjapplication.activity.RelativeLayoutTestActivity", "", "", "", "void"), 46);
  }

  private void testAOP()
  {
    JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_0, this, this);
    TraceAspect.aspectOf().methodAOPTest(localJoinPoint);
    int i = 0;
    for (int j = 0; j < 1000; j++)
      i++;
  }

  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130903043);
    this.btn_test = ((Button)findViewById(2131230727));
    this.btn_test.setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramAnonymousView)
      {
        RelativeLayoutTestActivity.this.testAOP();
      }
    });
    this.btn_test2 = ((Button)findViewById(2131230728));
    this.btn_test2.setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramAnonymousView)
      {
      }
    });
  }
}

再来看下call,代码如下:

 //call用法
  @Before("call(* com.example.myaspectjapplication.activity.RelativeLayoutTestActivity.testAOP(..))")
  public void methodAOPTest(JoinPoint joinPoint) throws Throwable {
    String key = joinPoint.getSignature().toString();
    Log.d(TAG, "methodAOPTest: " + key);
  }

编译之后的代码如下所示

package com.example.myaspectjapplication.activity;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import org.aspectj.lang.JoinPoint.StaticPart;
import org.aspectj.runtime.reflect.Factory;

public class RelativeLayoutTestActivity extends Activity
{
  private static final JoinPoint.StaticPart ajc$tjp_0;
  Button btn_test;
  Button btn_test2;

  static
  {
    ajc$preClinit();
  }

  private static void ajc$preClinit()
  {
    Factory localFactory = new Factory("RelativeLayoutTestActivity.java", RelativeLayoutTestActivity.class);
    ajc$tjp_0 = localFactory.makeSJP("method-call", localFactory.makeMethodSig("2", "testAOP", "com.example.myaspectjapplication.activity.RelativeLayoutTestActivity", "", "", "", "void"), 21);
  }

  private void testAOP()
  {
    int i = 0;
    for (int j = 0; j < 1000; j++)
      i++;
  }

  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130903043);
    this.btn_test = ((Button)findViewById(2131230727));
    this.btn_test.setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramAnonymousView)
      {
        RelativeLayoutTestActivity.access$000(RelativeLayoutTestActivity.this);
      }
    });
    this.btn_test2 = ((Button)findViewById(2131230728));
    this.btn_test2.setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramAnonymousView)
      {
      }
    });
  }
}

其实对照起来看就一目了然了,execution是在被切入的方法中,call是在调用被切入的方法前或者后。
对于Call来说:

Call(Before)
Pointcut{
    Pointcut Method
}
Call(After)

对于Execution来说:

Pointcut{
  execution(Before)
    Pointcut Method
  execution(After)
}

通配符使用
1、截获任何包中以类名以Activity的所有方法

//截获任何包中以类名以Activity的所有方法
   @Before("execution(* *..Activity+.*(..))")
  public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
  String key = joinPoint.getSignature().toString();
  Log.d(TAG, "onActivityMethodBefore: " + key+"\n"+joinPoint.getThis());
  }

运行效果如下:

aop18

2、 截获任何包中以类名以Activity、Fragment结尾的所有方法

//截获任何包中以类名以Activity、Fragment结尾的所有方法
    @Before("execution(* *..Activity+.*(..)) ||execution(* *..Fragment+.*(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
    String key = joinPoint.getSignature().toString();
    Log.d(TAG, "onActivityMethodBefore: " + key+"\n"+joinPoint.getThis());
    }

运行效果如下:

aop21

AspectJ性能消耗实战

1、创建注解

首先我们创建我们的Java注解。这个注解周期声明在 class 文件上(RetentionPolicy.CLASS),可以注解构造函数和方法(ElementType.CONSTRUCTOR 和 ElementType.METHOD)。因此,我们的 DebugTrace.java 文件看上是这样的:

/**
 * Indicates that the annotated method is being traced (debug mode only) and
 * will use {@link android.util.Log} to print debug data:
 * - Method name
 * - Total execution time
 * - Value (optional string parameter)
 */
@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD })
public @interface DebugTrace {}

2、性能监控计时类

/**
 * Copyright (C) 2014 android10.org. All rights reserved.
 * @author Fernando Cejas (the android10 coder)
 */
package org.android10.gintonic.internal;

import java.util.concurrent.TimeUnit;

/**
 * Class representing a StopWatch for measuring time.
 */
public class StopWatch {
  private long startTime;
  private long endTime;
  private long elapsedTime;

  public StopWatch() {
    //empty
  }

  private void reset() {
    startTime = 0;
    endTime = 0;
    elapsedTime = 0;
  }

  public void start() {
    reset();
    startTime = System.nanoTime();
  }

  public void stop() {
    if (startTime != 0) {
      endTime = System.nanoTime();
      elapsedTime = endTime - startTime;
    } else {
      reset();
    }
  }

  public long getTotalTimeMillis() {
    return (elapsedTime != 0) ? TimeUnit.NANOSECONDS.toMillis(endTime - startTime) : 0;
  }
}

3、DebugLog 类

我只是包装了一下 “android.util.Log”,因为我首先想到的是向 android log 中增加更多的实用功能。下面是代码:


package org.android10.gintonic.internal;

import android.util.Log;

/**
 * Wrapper around {@link android.util.Log}
 */
public class DebugLog {

  private DebugLog() {}

  /**
   * Send a debug log message
   *
   * @param tag Source of a log message. It usually identifies the class or activity where the log
   * call occurs.
   * @param message The message you would like logged.
   */
  public static void log(String tag, String message) {
    Log.d(tag, message);
  }
}

4、Aspect 类

@Aspect
public class TraceAspect {
  private static final String POINTCUT_METHOD =
      "execution(@org.android10.gintonic.annotation.DebugTrace * *(..))";

  private static final String POINTCUT_CONSTRUCTOR =
      "execution(@org.android10.gintonic.annotation.DebugTrace *.new(..))";

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

  @Pointcut(POINTCUT_CONSTRUCTOR)
  public void constructorAnnotatedDebugTrace() {}

  @Around("methodAnnotatedWithDebugTrace() || constructorAnnotatedDebugTrace()")
  public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
    //获取方法信息对象
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    //获取当前对象
    String className = methodSignature.getDeclaringType().getSimpleName();
    String methodName = methodSignature.getName();

    //获取当前对象,通过反射获取类别详细信息
    //String className2 = joinPoint.getThis().getClass().getName();

    //初始化计时器
    final StopWatch stopWatch = new StopWatch();
    //开始监听
    stopWatch.start();
    //调用原方法的执行。
    Object result = joinPoint.proceed();
    //监听结束
    stopWatch.stop();

    DebugLog.log(className, buildLogMessage(methodName, stopWatch.getTotalTimeMillis()));

    return result;
  }

  /**
   * Create a log message.
   *
   * @param methodName A string with the method name.
   * @param methodDuration Duration of the method in milliseconds.
   * @return A string representing message.
   */
  private static String buildLogMessage(String methodName, long methodDuration) {
    StringBuilder message = new StringBuilder();
    message.append("Gintonic --> ");
    message.append(methodName);
    message.append(" --> ");
    message.append("[");
    message.append(methodDuration);
    message.append("ms");
    message.append("]");

    return message.toString();
  }
}

解释一下几个重点:

1、我们声明了两个作为 pointcuts 的 public 方法,筛选出所有通过 “org.android10.gintonic.annotation.DebugTrace” 注解的方法和构造函数。
2、我们使用 “@Around” 注解定义了“weaveJointPoint(ProceedingJoinPoint joinPoint)”方法,使我们的代码注入在使用"@DebugTrace"注解的地方生效。
3、“Object result = joinPoint.proceed();”这行代码是被注解的方法执行的地方,相当于onMeasure执行的地方。因此,在此之前,我们启动我们的计时类计时,在这之后,停止计时。
4、最后,我们构造日志信息,用 Android Log 输出。

5、MyLinearLayout类

/**
 * Copyright (C) 2014 android10.org. All rights reserved.
 * @author Fernando Cejas (the android10 coder)
 */
package com.example.myaspectjapplication.component;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import org.android10.gintonic.annotation.DebugTrace;

/**
 *
 */
public class MyLinearLayout extends LinearLayout {

  public MyLinearLayout(Context context) {
    super(context);
  }

  public MyLinearLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public MyLinearLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }

  @DebugTrace
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    sleep(10);
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
  }

  /**
   * Method for sleeping. Testing purpose. DON'T DO SOMETHING LIKE THIS!!!
   *
   * @param millis Amount of millis to sleep.
   */
  private void sleep(long millis) {
    try {
      Thread.sleep(millis);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

6、LinearLayoutTestActivity的布局如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

  <com.example.myaspectjapplication.component.MyLinearLayout
      android:id="@+id/linearLayoutOne"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      />

  <com.example.myaspectjapplication.component.MyLinearLayout
      android:id="@+id/linearLayoutTwo"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      />

  <com.example.myaspectjapplication.component.MyLinearLayout
      android:id="@+id/linearLayoutThree"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      />
</LinearLayout>

只要在我们想监控的方法上加上 @DebugTrace即可,我在这里给onMeasure方法上注解,当我进入LinearLayoutTestActivity 类时,运行如下:

aop17

你可以在项目的任何一个方法中加上@DebugTrace注解,监控性能状况。

既然已经能够捕捉用户一切行为了,接下来应该是根据自己的业务规则来选择自己的一套策略来使用这些用户行为数据如何使用了。

本人演示环境
若dome下载下来直接导入到as中不能运行,建议先调整环境,把dome先跑起来,看看效果之后,自己再折腾,谢谢。

API 21: Android 5.0 (Lollipop)

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        //classpath 'com.android.tools.build:gradle:2.3.1'
        classpath 'com.android.tools.build:gradle:2.1.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.12'
}

#Sat Jul 01 17:37:00 CST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip

最后附上Dome下载地址:
http://download.csdn.net/download/xinanheishao/9886917

apk反编译工具下载地址
http://blog.csdn.net/xinanheishao/article/details/74278192

Flutter电商实战项目:https://github.com/dechengyang/ydc_flutter_app

如果对你有所帮助的话,赏我1元奶粉钱吧,多谢!

微信:

001

支付宝:

002

发布了53 篇原创文章 · 获赞 58 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/XiNanHeiShao/article/details/74082605