AspectJ 在 Android 中的使用

AspectJ 在 Android 中的使用

在介紹 AspectJ 之前,我們先看看常見(jiàn)的幾種編程架構(gòu)思想。

  • 面向?qū)ο缶幊?Object Oriented Programming
  • 面向過(guò)程編程 Procedure Oriented Programming
  • 面向切面編程 Aspect Oriented Programming

面向?qū)ο蟆⒚嫦蜻^(guò)程、面向切面, 這三種是我們常見(jiàn)的三種編程架構(gòu)思想,在日常的編程中, OOP 是 Android 開(kāi)發(fā)中最常見(jiàn)的,其他的兩種比較少見(jiàn)。

一、AOP

AOP 是面向切面編程,它在我們的日志系統(tǒng)、權(quán)限管理方面有著比較好的應(yīng)用。
在項(xiàng)目中,我們的很多功能都是分散到各個(gè)模塊,例如日志打印,AOP 的目標(biāo)就是要把這些功能集中起來(lái),放到一個(gè)統(tǒng)一的地方來(lái)控制和管理。

二、AspectJ

AOP 是一種編程的思想,在具體的編程中需要實(shí)際的工具去實(shí)現(xiàn)這套思想。 AspectJ 就是這樣的一個(gè)工具。使用 AspectJ 有兩種方式

    1. 完全使用 AspectJ 的語(yǔ)言開(kāi)發(fā);
    1. 使用 AspectJ 注解,完全的使用純 Java 開(kāi)發(fā)

我們后續(xù)講的,基本上都是以 AspectJ 注解的方法,同時(shí)在最后也會(huì)附上 AspectJ 和 AspectJ 注解的等價(jià)。

1.AspectJ 語(yǔ)法

這里只是介紹簡(jiǎn)單的一些概念,如果想要去了解深入的用法,可參考文后的鏈接,去官網(wǎng)查看。

1. JoinPoint

JoinPoint: A particular point in a program that might be the target of code injection.
JoinPoint 簡(jiǎn)單一點(diǎn)說(shuō)就是程序運(yùn)行時(shí)要執(zhí)行一些動(dòng)作的點(diǎn)。

AspectJ 中可以選擇的 JoinPoint

JoinPoint 說(shuō)明 示例
method call 函數(shù)調(diào)用 例如調(diào)用 Log.e( )
method execution 函數(shù)執(zhí)行 例如 Log.e( ) 的執(zhí)行內(nèi)部。
method call 是調(diào)用某個(gè)函數(shù)的地方
execution 是某個(gè)函數(shù)執(zhí)行的內(nèi)部
constructor call 構(gòu)造函數(shù)調(diào)用 和 method call 類似
constructor execution 構(gòu)造函數(shù)執(zhí)行 和 method execution 類似
field get 獲取某個(gè)變量 例如讀取 MainActivity.mTest 成員
field set 設(shè)置某個(gè)變量 例如設(shè)置 MainActivity.mTest 成員
pre-initialization Object 在構(gòu)造函數(shù)中做的一些工作
initialization Object 在構(gòu)造函數(shù)中做的工作
static initialization 類初始化 例如類的 static{}
handler 異常處理 例如 try catch(xxx) 中,對(duì)應(yīng) catch 內(nèi)的執(zhí)行
advice execution AspectJ 的內(nèi)容

JoinPoint 的選擇要結(jié)合下面的 Pointcuts 表達(dá)式來(lái)看

2. Pointcut

Pointcut: An expression which tell a code injection tool where to inject a particular piece of code
Pointcut 簡(jiǎn)單的說(shuō)就是從一堆的 JoinPoint 中挑選感興趣的 JoinPoint 的表達(dá)式。

例如

pointcut anyCall(): call(* *.println(..)) && !within(TestAspect);

在 AspectJ 的語(yǔ)言中定義一個(gè) Pointcout 需要用關(guān)鍵詞 pointcut .
上面的這里是

  • pointcut: 是定一個(gè) Pointcut 的關(guān)鍵詞
  • anyCall(): 是 Pointcut 的名稱
  • call : 表示 JoinPoint 的類型為 call
  • 第一個(gè) '*' 號(hào)是返回值, ‘*’ 代表是任意返回值; 第二個(gè) ‘*’ 號(hào)代表是包名,‘*’ 代表是任意包名,這邊表明我們是選擇任意包名下的 println 函數(shù);在 (..) 中指定參數(shù)類型,‘..’ 通配符表示任意類型;
  • &&! 表示組合條件,有 &&, || 以及 !
  • within(TestAspect): within 是 JoinPoint 間接選擇過(guò)濾的一個(gè)方法,后面會(huì)講到。 !within(TestAspect) 表示調(diào)用者的類型不是 TestAspect.
3. JointPoint 的選擇

JointPoint 的選擇有分成直接選擇和間接選擇兩種方式

  • JointPoint 的直接選擇就是通過(guò)和 Pointcut 的語(yǔ)法一一對(duì)應(yīng)關(guān)系中選擇;
  • JointPoint 的間接選擇就是通過(guò)一些通配符進(jìn)行篩選過(guò)濾的選擇,上面例子中的 within 就是間接選擇的一種。
1.JointPoint 直接選擇

JoinPoint 的選擇策略和 Pointcut 的語(yǔ)法對(duì)應(yīng)關(guān)系

JoinPoint Category Pointcut Syntax
Method execution execution(MethodSignature)
Method call call(MethodSignature)
Constructor execution execution(ConstructorSignature)
Constructor call call(ConstructorSignature)
Class initialization staticinitialization(TypeSignature)
Field read access get(FieldSignature)
Field write access set(FieldSignature)
Exception handler execution handler(TypeSignature)
Object initialization initialization(ConstructorSignature)
Object pre-initialization preinitialization(ConstructorSignature)
Advice execution adviceexecution()

JoinPoint 的策略的選擇對(duì)應(yīng)著不同 Pointcut,特別是 Pointcut 里面有著不同的 Signature。

以下有詳細(xì)的說(shuō)明:

Method Signature 表達(dá)式
語(yǔ)法

@注解 訪問(wèn)權(quán)限 返回值的類型 包名.函數(shù)名(參數(shù))
例子:
@before("execution(* android.app.Activity.on**(..))");

  • 注解: 是可選項(xiàng); 這里是 @before,關(guān)于注解的在后面 Adivce 中有更詳細(xì)的說(shuō)明

  • 訪問(wèn)權(quán)限: 可選項(xiàng); 有 public, private, protected 類型;例子沒(méi)有設(shè)置

  • 返回值的類型: 與普通函數(shù)的返回值類型是一樣的,如果不限定類型,用通配符 * 表示。例子中是 *

  • 包名.函數(shù)名:用于查找匹配的函數(shù),可以使用通配符

    通配符的類型

    • ' * '表示用于匹配處 . 號(hào)之外的任意字符;
    • ' .. ' 表示任意子 package
    • ' + '號(hào)表示子類


    例子:

    • java.*.Data: 可以表示 java.sql.Data ,也可以表示 java.util.Date;
    • Test* : 表示Test開(kāi)頭的函數(shù),可以表示 TestBase, 也可以表示 TestDervied
    • java..* : 表示 java 任意子類
    • java..*Model+: 表示 Java 任意 package 中名字以 Model 結(jié)尾的子類,比如 TabelModel, TreeModel 等
  • 函數(shù)參數(shù)
    參數(shù)有不同的型式

    • (int, char): 表示參數(shù)只有兩個(gè), 并且第一個(gè)參數(shù)是 int, 第二個(gè)參數(shù)是 char;
    • (String, ..): 表示參數(shù)至少有一個(gè)。并且第一個(gè)參數(shù)是 String, 后面參數(shù)類型不限。在參數(shù)匹配中, .. 代表任意參數(shù)個(gè)數(shù)和類型;
    • (Oject ...): 表示不定個(gè)數(shù)的參數(shù),并且類型都是 Object, 這里的 ... 不是通配符,而是 java 中不定參數(shù)的意思;

Constructor Signature 表達(dá)式
和 Method Signature 類似
不同點(diǎn):
構(gòu)造函數(shù)沒(méi)有返回值,并且函數(shù)名必須叫 new
例子:

public *..TestDeived.new(..)

  • public: 表示選擇 public 訪問(wèn)權(quán)限的
  • *.. : 代表任意包名
  • TestDeived.new: 代表 TestDerived 的構(gòu)造函數(shù)
  • (..): 代表參數(shù)個(gè)數(shù)和類型都是任意的

Field Signature表達(dá)式
語(yǔ)法

@注解 訪問(wèn)權(quán)限 類型 類名.成員變量名

  • @注解和訪問(wèn)權(quán)限是可選的
  • 類型:成員變量類型, * 表示任意類型
  • 類名.成員變量名: 成員變量名可以是*, 代表任意成員變量

例子, 用 AspectJ 打印成員變量賦值前后的值

// TraceAspect.java set field 的切面
private static final String POINTCUT_FILEED =
        "set(int org.android10.viewgroupperformance.activity.MainActivity.mTest) && args(newValue) && target(t)";

@Before(POINTCUT_FILEED)
public void onFiled(JoinPoint joinPoint, Object newValue, Object t) throws IllegalAccessException {
    Object object = joinPoint.getThis();

    FieldSignature fieldSignature = (FieldSignature) joinPoint.getSignature();
    String fileName = fieldSignature.getName();
    Field field = fieldSignature.getField();
    field.setAccessible(true);
    Class clazz = fieldSignature.getFieldType();
    String clazzName = clazz.getSimpleName();

    Object oldValue = field.get(t);

    Log.i("MainActivity", "\nonFiled value = " + newValue.toString() + "\n fieldSignature =" + fieldSignature.toString()
                + "\nfield = " + field.toString() + " +  \nFileName = " + fileName
                + "\nclazzName = " + clazzName + " \noldValue = " + oldValue.toString() );
}

// 在 MainActivity.java 中
@Override
protected void onResume() {
        super.onResume();
        mTest = 100;
}

打印結(jié)果

onFiled value = 100
fieldSignature =int org.android10.viewgroupperformance.activity.MainActivity.mTest
field = private int org.android10.viewgroupperformance.activity.MainActivity.mTest +
FileName = mTest
clazzName = int
oldValue = -1

TypeSignature表達(dá)式
例子:
staticinitlization(test..TestBase): 表示 TestBase 類的 static block
handler(NullPointException): 表示 catch 到 NullPointerException 的 JPoin

2.JointPoint 間接選擇

JointPoint 的直接選擇是通過(guò) Signature 信息匹配的,除此之外還有其他的方式,這些方式都可以歸類到間接選擇

關(guān)鍵詞 說(shuō)明 實(shí)例
within(TypePattern) TypePattern 表示 package 或者類
TypePattern 可以使用通配符
表示某個(gè) Package 或者類中的 Point
within(Test): Test 類中(包括內(nèi)部類)所有的 JointPoint
withcode(Constructor Signature|Method Signature) 表示某個(gè)構(gòu)造函數(shù)或其他函數(shù)執(zhí)行過(guò)程涉及到的 JointPoint withinCode(* Test.testMethod(..))
表示 testMethod 涉及的 JointPoint
withinCode(*.Test.new(..))
表示 Test 的構(gòu)造函數(shù)涉及的 JointPoint
cflow(pointcuts) cflow 表示 call flow
cflow 的條件是一個(gè) pointcut
cflow(call Test.testMethod)
表示調(diào)用 Test.testMethod 函數(shù)是所包含的 JointPoint,包含 testMethod 的 call 這個(gè) JointPoint 本身
cflowbelow(pointcuts) cflowbelow 表示不包含自身的 cflow cflowbelow(call Test.testMethod)
表示調(diào)用 Test.testMethod 函數(shù)是所包含的 JointPoint, 不包含 testMethod 的 call 這個(gè) JointPoint 本身
this(Type) JointPoint 的 this 對(duì)象是 Type 類型 JPoint是代碼段(不論是函數(shù),異常處理,static block),從語(yǔ)法上說(shuō),它都屬于一個(gè)類。如果這個(gè)類的類型是Type標(biāo)示的類型,則和它相關(guān)的JPoint將全部被選中。
target(Type) JoinPoint 的 target 對(duì)象是 Type 類型 和this相對(duì)的是target。不過(guò)target一般用在call的情況。call一個(gè)函數(shù),這個(gè)函數(shù)可能定義在其他類。比如testMethod是TestDerived類定義的。那么target(TestDerived)就會(huì)搜索到調(diào)用testMethod的地方。但是不包括testMethod的execution JointPoint
args(TypeSignature) 用來(lái)對(duì) JointPoint 的參數(shù)進(jìn)行條件搜索 例如 arg(int, ..)
表示第一個(gè)參數(shù)是 int, 后面參數(shù)個(gè)數(shù)和類型不限的 JointPoint
3.call 與 execution 區(qū)別

當(dāng) call 捕獲 joinPoint 時(shí),捕獲的簽名方法的調(diào)用點(diǎn);execution 捕獲 joinPoint 時(shí),捕獲的則是執(zhí)行點(diǎn)
兩個(gè)的區(qū)別在于一個(gè)是 ”調(diào)用點(diǎn)“, 一個(gè)是 ”執(zhí)行點(diǎn)“

對(duì)于 call 來(lái)講

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

對(duì)于 execution 來(lái)說(shuō)

Pointcut {
   Execution(Before)
   Pointcut Method
   Execution(After)
}
3.AspectJ 注解的等價(jià)

AspectJ 提供了相應(yīng)的注解,注解的方式和 AspectJ 語(yǔ)言編寫是等效的。我們?cè)?Android 中一般也是采用注解的方式

Aspect

public aspect Foo{}
等效
@Aspect
public class Foo{}

call

@Pointcut("call(* .(..))")
void anyCall(){}
等效
pointcut anyCall(): call(* .(..))

要綁定參數(shù)的時(shí)候,只需要將參數(shù)作為備注解的方法的參數(shù)即可

@Pointcut("call(* .(int)) && arg(i) && target(callee)")
void anyCall(int i, Fool callee){}
等效
pointcut anyCall(int i, Foo callee): call(* .(int)) && arg(i) && target(callee){};
說(shuō)明要先定義參數(shù) int i, Foo callee

before

@Before("call(* org.android10.viewgroupperformance.activity..(..)) && this(foo)")
public void callFromFoo(Foo foo){}
等效
before(Foo foo): call(
org.android10.viewgroupperformance.activity..*(..)) && this(foo){}

returning

@AfterReturning(pointcut="call(Foo+.new(..))", returning="f")
public void itsAFoo(foo f){}
等效
after returning(Foo f): call(Foo+.new(..)){}

其他的表達(dá)式也是類似的

4. Advice

Advice: Advice defines pieces of aspect implementation that execute at well-defined points in the execution of the program.

通過(guò)前面的 Pointcuts 找到了相應(yīng)的 JointPoint, 需要對(duì)這些 JointPoint 最一些事情,相當(dāng)于對(duì) JointPoint 進(jìn)行 hook, 這就是 advice 要做的事。

advice 的分類

關(guān)鍵詞 說(shuō)明 示例
before() before advice 表示在 JointPoint 執(zhí)行之前要干 的事
after() after advice 表示在 JointPoint 執(zhí)行之后要干的事
after(): returning(返回值類型)
after():throwing(異常類型)
returning 和 throwing 后面都可以指定具體的類型,如果不指定則匹配類型不限制 假設(shè) JointPoint 是一個(gè)函數(shù)調(diào)用
那么函數(shù)調(diào)用執(zhí)行完有兩種方式退出
一個(gè)是正常的 return, 另一個(gè)是拋異常
after() 默認(rèn)包括 returing 和 throwing 兩種情況
返回值類型
around()
around 替代原來(lái)的 JointPoint around 替代了原來(lái)的 JointPoint,如果要執(zhí)行原 JointPoint 的話,需要調(diào)用 procced

例子:
我們需要在 Activity 中的 onResume 方法調(diào)用前后輸出

// MainActivity.java
@Override
protected void onResume() {
    super.onResume();
    Log.i(TAG, "--- onResume---");
}

// TraceAspect.java
private static final String POINTCUT_ONMETHOD =
        "execution(* android.app.Activity.on**(..))";

@Before(POINTCUT_ONMETHOD)
public void beforeOnMethod(JoinPoint joinPoint) {
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    String className = methodSignature.getDeclaringType().getSimpleName();
    String methodName = methodSignature.getName();
    Log.i(className, "before " + methodName + " log");

}

@After(POINTCUT_ONMETHOD)
public void onMethLog(JoinPoint joinPoint){
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    String className = methodSignature.getDeclaringType().getSimpleName();
    String methodName = methodSignature.getName();
    Log.i(className, "after " + methodName + " log");
}

查看輸出


aspectj_1.png

改成 Around 的方式

// TraceAspect.java
private static final String POINTCUT_ONMETHOD =
        "execution(* android.app.Activity.on**(..))";
@Pointcut(POINTCUT_ONMETHOD)
public void annotationOnMethodTrace(){

}

@Around("annotationOnMethodTrace()")
public Object weaveOnMethodJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

  MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  String className = methodSignature.getDeclaringType().getSimpleName();
  String methodName = methodSignature.getName();

  Log.i("MainActivity", "before joinPoint proceed className = " + className + " methodName = " + methodName);

  Object result  = joinPoint.proceed();
  Log.i("MainActivity", "after joinPoint proceed className = " + className + " methodName = " + methodName);

  return result;
}

輸出

aspectj_2.png

從上面例子的輸出我們可以看到 around 等價(jià)于 before + after, 另外 JointPoint#proceed 是原來(lái)的 JointPoint,在這里是 onResume 方法, 輸出中的 “--- onResume---” 就是在 onResume 中打印的。

5. 參數(shù)傳遞和 JointPoint 信息

經(jīng)過(guò)前面的幾個(gè)步驟,我們已經(jīng)拿到了 JointPoint,但是我們經(jīng)常需要對(duì)一些 advice 傳入?yún)?shù),然后進(jìn)行處理的。例如如果傳入的參數(shù)不合法,就不用調(diào) JointPoint#proceed 方法處理了。

參數(shù)傳遞

advice 參數(shù)的方法由 this, target(), args()

  • this(Type or Id): 捕獲當(dāng)前對(duì)象(被綁定 this)實(shí)例執(zhí)行的連接點(diǎn) -- 實(shí)例由 Type 或者 Id 描述
  • target(Type or Id): 捕獲目標(biāo)對(duì)象(被應(yīng)用與對(duì)象上的調(diào)用和屬性操作)實(shí)例的連接點(diǎn) -- 實(shí)例由 Type 和 Id 描述(必須綁定和封裝后放入通知或者切點(diǎn)定義)。它不匹配任何靜態(tài)的調(diào)用、應(yīng)用和設(shè)置成員。
  • args(Type or Id): 捕獲具有適當(dāng)類型樣式的實(shí)例連接點(diǎn)

下面是例子說(shuō)明
我們?cè)?MainActivity 定義一個(gè)成員變量 mTest, 初始值為 -1,在 OnResume() 方法對(duì)它進(jìn)行賦值,用 target 和 args 對(duì) mTest 賦值前后的值進(jìn)行監(jiān)聽(tīng)

// MainActivity.java
private int mTest = -1;
@Override
protected void onResume() {
    super.onResume();
    Log.i(TAG, "--- onResume---");
    mTest = 100;
}

// TraceAspect.java
// set field 的切面
private static final String POINTCUT_FILEED =
        "set(int org.android10.viewgroupperformance.activity.MainActivity.mTest) && args(newValue) && target(t)";

@Before(POINTCUT_FILEED)
public void onFiled(JoinPoint joinPoint, Object newValue, Object t) throws IllegalAccessException {

    FieldSignature fieldSignature = (FieldSignature) joinPoint.getSignature();
    String fileName = fieldSignature.getName();
    Field field = fieldSignature.getField();
    field.setAccessible(true);
    Class clazz = fieldSignature.getFieldType();
    String clazzName = clazz.getSimpleName();

    Object oldValue = field.get(t);

    Log.i("MainActivity",
               "\nonFiled value = " + newValue.toString()
                    + "\ntarget = " + t.toString()
                    + "\n fieldSignature =" + fieldSignature.toString()
                    + "\nfield = " + field.toString()
                    + "\nFileName = " + fileName
                    + "\nclazzName = " + clazzName
                    + " \noldValue = " + oldValue.toString() );
}

我們看看輸出

aspectj_3.png

定義切面表達(dá)式使用 args(newValue) && target(t) 它們的參數(shù)值 newValue, t,必須要和方法中的定義的對(duì)的上

public void onFiled(JoinPoint joinPoint, Object newValue, Object t)

JointPoint 信息
在 advice 中我們可以拿到 JointPoint 的信息,一般包含

  • JointPoint 對(duì)象信息:例如參數(shù)、前面之類的

JoinPoint.getSignature() 包含有

  1. MethodSignature 方法的簽名
  2. FieldSignature 成員變量的簽名
  3. ConstructorSignature 構(gòu)造函數(shù)的簽名
  4. InitializerSignature 初始化的簽名

不同的簽名對(duì)應(yīng)不同的場(chǎng)景

  • JointPoint 源代碼部分的信息,例如類型、所處的位置
  • JointPoint 靜態(tài)部分信息

例子:

@Around("methodAnnotatedWithDebugTrace() || constructorAnnotatedDebugTrace()")
public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
    // joint 對(duì)象信息
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    String className = methodSignature.getDeclaringType().getSimpleName();
    String methodName = methodSignature.getName();
    
    // 源代碼部分信息
    SourceLocation sourceLocation = joinPoint.getSourceLocation();
    String fileName = sourceLocation.getFileName();
    int line = sourceLocation.getLine();
    String soucreClassName = sourceLocation.getWithinType().getName();
    DebugLog.log(className, "\nfileName = " + fileName + "\nline = " + line + "\nsoucreClassName = " + soucreClassName);
        
    // 靜態(tài)部分
    JoinPoint.StaticPart staticPart = joinPoint.getStaticPart();
        
return result;
}

更詳細(xì)的信息參考文檔
https://www.eclipse.org/aspectj/doc/released/runtime-api/index.html

總結(jié)一下,使用 AspectJ 的步驟:

  1. 設(shè)置 Pointcut 的表達(dá)式
  2. 選擇相應(yīng)的 advice
  3. 對(duì) JointPoint 或參數(shù)進(jìn)行相應(yīng)的處理

三、AspectJ 集成在 Android studio 中

前面已經(jīng)介紹完了 AspectJ, 那接下來(lái)看看在 Android 中的實(shí)際使用;
實(shí)例代碼是在這個(gè)例子上進(jìn)行修改的Android-AOPExample

1. Library 庫(kù)依賴方式使用

項(xiàng)目的結(jié)構(gòu)如下

aspectj_4.png

在 library 項(xiàng)目 gintoinc 的 build.gradle 文件要添加 aspectj 的依賴

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

apply plugin: 'com.android.library'

repositories {
  mavenCentral()
}

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

android {
  compileSdkVersion 21
  buildToolsVersion '21.1.2'

  lintOptions {
    abortOnError false
  }
}

// -showWeaveInfo,輸出編織過(guò)程信息
// -1.5 設(shè)置規(guī)范1.5,匹配java1.5
// -inpath class文件目錄或者jar包, 源字節(jié)碼,需要處理的類
// -aspectpath  定義的切面類
// -d 存放編輯產(chǎn)生的class文件
// -classpath ,所有class文件,源class,java包,編織時(shí)需要用到的一些處理類
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;
      }
    }
  }
}

在引用庫(kù)工程的工程 build.gradle 也要進(jìn)行相應(yīng)的配置

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

apply plugin: 'com.android.application'

repositories {
  mavenCentral()
}

dependencies {
  compile project(':gintonic')
  compile 'org.aspectj:aspectjrt:1.8.1'
}

android {
  compileSdkVersion 21
  buildToolsVersion '21.1.2'

  defaultConfig {
    applicationId 'android10.org.viewgroupperformance'
    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;
      }
    }
  }
}

文章Aspect Oriented Programming in Android 是通過(guò)注解去查看方法執(zhí)行的時(shí)間,我們?cè)谶@個(gè)基礎(chǔ)上進(jìn)行修改,去監(jiān)聽(tīng)一個(gè)成員變量賦值變化的監(jiān)聽(tīng)。

我們需要監(jiān)聽(tīng) MainActivitymTest

private int mTest = -1;
@Override
protected void onResume() {
    super.onResume();
    Log.i(TAG, "--- onResume---");
    mTest = 100;
}

第一步. 設(shè)置 Pointcut 的表達(dá)式
TraceAspect.java

 // set field 的切面
    private static final String POINTCUT_FILEED =
            "set(int org.android10.viewgroupperformance.activity.MainActivity.mTest) && args(newValue) && target(t)";
            

根據(jù) JoinPoint 的選擇策略和 Pointcut 的語(yǔ)法對(duì)應(yīng)關(guān)系,成員變量選擇的 set, 參數(shù)傳遞的監(jiān)聽(tīng)使用 args(newValue) && target(t)

第二步. 選擇相應(yīng)的 advice

@Before(POINTCUT_FILEED)
public void onFiled(JoinPoint joinPoint, Object newValue, Object t) throws IllegalAccessException {
    
    ...
    
}

這里我們選擇的是 Before, 注意在第一步 args(newValue) && target(t) 中的 newValue 和 t 是要在 advice 函數(shù)中定義的 Object newValue, Object t.

第三步. 對(duì) JointPoint 或參數(shù)進(jìn)行相應(yīng)的處理

@Before(POINTCUT_FILEED)
public void onFiled(JoinPoint joinPoint, Object newValue, Object t) throws IllegalAccessException {

    FieldSignature fieldSignature = (FieldSignature) joinPoint.getSignature();
    String fileName = fieldSignature.getName();
    Field field = fieldSignature.getField();
    field.setAccessible(true);
    Class clazz = fieldSignature.getFieldType();
    String clazzName = clazz.getSimpleName();

    // 獲取舊的值
    Object oldValue = field.get(t);

    Log.i("MainActivity",
               "\nonFiled value = " + newValue.toString()
                    + "\ntarget = " + t.toString()
                    + "\n fieldSignature =" + fieldSignature.toString()
                    + "\nfield = " + field.toString()
                    + "\nFileName = " + fileName
                    + "\nclazzName = " + clazzName
                    + " \noldValue = " + oldValue.toString() );
    
}

通過(guò) JoinPoint 獲取相應(yīng)的信息。

在 build 之后,在 app/intermediates/classes/debug 目錄下的

aspectj_5.png

MainActivity.class 文件中

protected void onResume() {
    super.onResume();
    Log.i("MainActivity", "--- onResume---");
    byte var1 = 100;
    JoinPoint var3 = Factory.makeJP(ajc$tjp_2, this, this, Conversions.intObject(var1));
    TraceAspect.aspectOf().onFiled(var3, Conversions.intObject(var1), this);
    this.mTest = var1;
}

我們發(fā)現(xiàn)上面生成了一下代碼,這些生成的代碼就是 AspectJ 根據(jù)我們前面設(shè)置的 Pointcut 和 adive 生成的。

輸出
在 build 之后,在 app/intermediates/classes/debug 目錄下的

<img src="AspectJ在Android中的使用/aspectj_3.png" width="70%" height="100%">

2. Plugin 插件方式使用

如果是多個(gè) Module 都依賴 AspectJ, 可以寫成 plugin 插件的型式

aspectj_6.png

關(guān)于如果使用 Android studio 的 Plugin 插件,可以去查看相關(guān)資料

項(xiàng)目的 build.gradle

buildscript {
    repositories {
        mavenCentral()
        // 本地倉(cāng)庫(kù)
        maven {
            url uri('repo')
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0'
        // 引入插件
        classpath 'com.yxhuang:autotrack.android:1.0.1'
    }
}

allprojects {
  repositories {
    mavenCentral()
      maven {
          url 'https://maven.google.com/'
          name 'Google'
      }
  }
}

//task wrapper(type: Wrapper) {
//  gradleVersion = '2.12'
//}
task clean(type: Delete) {
    delete rootProject.buildDir
}

gintonic 的 build.gradle 文件

buildscript {
  repositories {
    mavenCentral()
    // 本地代碼倉(cāng)
    maven{
      url uri('../repo')
    }
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:2.1.0'
    classpath 'com.yxhuang:autotrack.android:1.0.1' // 引用插件
  }
}

apply plugin: 'com.android.library'
apply plugin: 'com.yxhuang.android' // 引用插件

repositories {
  mavenCentral()
}

dependencies {
}

android {
  compileSdkVersion 21
  buildToolsVersion '21.1.2'

  lintOptions {
    abortOnError false
  }
}

app module 的 build.gradle 文件

apply plugin: 'com.android.application'
apply plugin: 'com.yxhuang.android'  //引入插件

buildscript {
  repositories {
    mavenCentral()

    // 本地代碼倉(cāng)
    maven{
      url uri('../repo')
    }
  }
  dependencies {
    classpath 'com.yxhuang:autotrack.android:1.0.1' //引入插件
  }
}

repositories {
  mavenCentral()
}

dependencies {
  compile project(':gintonic')
}

android {
  compileSdkVersion 21
  buildToolsVersion '21.1.2'

  defaultConfig {
    applicationId 'android10.org.viewgroupperformance'
    minSdkVersion 15
    targetSdkVersion 21
  }

  lintOptions {
    abortOnError true
  }
}

具體的代碼可以去 github AndroidAopDemo 選擇 tag v2 即可。

四、參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,772評(píng)論 3 422
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,947評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,201評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,960評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,350評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,549評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,104評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,914評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,089評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,340評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,753評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,007評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,834評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,106評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容