1. 程式人生 > >Android中的AOP程式設計之AspectJ實戰實現資料埋點

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框架,這個問題現在作者依然沒有解決,希望作者能夠快速解決。

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來說:

CallBefore)
Pointcut{
    Pointcut Method
}
CallAfter

對於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