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 有兩種方式
- 完全使用 AspectJ 的語(yǔ)言開(kāi)發(fā);
- 使用 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");
}
查看輸出
改成 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;
}
輸出
從上面例子的輸出我們可以看到 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() );
}
我們看看輸出
定義切面表達(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() 包含有
- MethodSignature 方法的簽名
- FieldSignature 成員變量的簽名
- ConstructorSignature 構(gòu)造函數(shù)的簽名
- 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 的步驟:
- 設(shè)置 Pointcut 的表達(dá)式
- 選擇相應(yīng)的 advice
- 對(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)如下
在 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) MainActivity 中 mTest
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 目錄下的
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 插件的型式
關(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 即可。
四、參考
- 1.深入理解Android之AOP
- 2.Aspect Oriented Programming in Android
- 3.《Android 全埋點(diǎn)解決方案》第 8 章,AppClick 全埋點(diǎn)方案5:AspectJ
- 4.極客時(shí)間專欄《Android 開(kāi)發(fā)高手課》第 27 講, 編譯插樁的三種方法: AspectJ, ASM, ReDex
- 5.AspectJ 官方手冊(cè)