版權聲明:本文為博主原創文章,未經博主允許不得轉載。
什么是AOP
AOP是 Aspect Oriented Programming 的縮寫,即面向切面編程,和平常遇到的面向對象OOP編程不一樣的是,OOP是將功能模塊化對象化,AOP是針對同一類的問題統一化處理。例如做日志埋點,性能監控,動態權限控制等。
AspectJ
AspectJ實際上是對AOP編程的實踐,目前還有很多的AOP實現,如ASMDex,但筆者選用的是AspectJ。
在Android項目中使用AspectJ
如果使用原生AspectJ在項目中配置會非常麻煩,在GitHub上有個開源的SDK gradle_plugin_android_aspectjx基於gradle配置即可。
接入說明
請自行查看開源項目中的接入配置過程
AspectJ 之 Join Points介紹
Join Points在AspectJ中是關鍵的概念。Join Points可以看做是程序運行時的一個執行點,比如:一個函數的調用可以看做是個Join Points,相當於代碼切入點。但在AspectJ中,只有下面幾種執行點是認為是Join Points:
Join Points | 說明 | 實例 |
---|---|---|
method call | 函數調用 | 比如調用Log.e(),這是一個個Join Point |
method execution | 函數執行 | 比如Log.e()的執行內部,是一處Join Points。注意這里是函數內部 |
constructor call | 構造函數調用 | 和method call 類似 |
constructor execution | 構造函數執行 | 和method execution 類似 |
field get | 獲取某個變量 | 比如讀取DemoActivity.debug成員 |
field set | 設置某個變量 | 比如設置DemoActivity.debug成員 |
pre-initialization | Object在構造函數中做的一些工作。 | - |
initialization | Object在構造函數中做的工作。 | - |
static initialization | 類初始化 | 比如類的static{} |
handler | 異常處理 | 比如try catch 中,對應catch內的執行 |
advice execution | 這個是AspectJ 的內容 | - |
Pointcuts 介紹
一個程序會有多個Join Points,即使同一個函數,也還分為call 和 execution 類型的Join Points,但並不是所有的Join Points 都是我們關心的,Pointcuts 就是提供一種使得開發者能夠值選擇所需的JoinPoints的方法。
Advice
Advice就是我們插入的代碼可以以何種方式插入,有Before 還有 After、Around。
下面看個例子:
@Before(“execution(* android.app.Activity.on**(..)))”)
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable{
}
這里會分成好幾個部分,我們依次來看:
- @Before: Advice, 也就是具體的插入點
- execution:處理Join Point的類型,例如call、execution
(* android.app.Activity.on**(..))
: 這個是最重要的表達式,第一個*
表示返回值,*
表示返回值為任意類型,后面這個就是典型的包名路徑,其中可以包含*
來進行通配,幾個*
沒有區別。同時這里可以通過&&、||、!
來進行條件組合。()代表這個方法的參數,你可以指定類型,例如android.os.Bundle,或者 (..) 這樣來代表任意類型、任意個數的參數。- public void onActivityMehodBefore: 實際切入的代碼。
Before 和 After 其實還是很好理解的,也就是在Pointcuts之前和之后,插入代碼,那么Android呢,從字面含義上來講,也就是在方法前后各插入代碼,他包含了 Before和 After 的全部功能,代碼如下:
@(“execution(* com.xys.aspectjxdemo.MainActivity.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()代表執行原始的方法,在這之前、之后,都可以進行各種邏輯處理。
自定義Pointcuts
自定義Pointcuts可以讓我們更加精准的切入一個或多個指定的切入點。
首先我們要定義一個注解類
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface DebugTrace {
}
在需要插入代碼的地方加入這個注解,例如在MainActivity中加入:
public class MainActivity extends AppCompatActivity{
final String TAG = MainActivity.class.getSimpleName();
@Override
protedcted void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
logTest();
}
@DebugTrace
public void logTest(){
Log.e(TAG,”log test");
}
}
最后創建切入代碼
@Pointcut(“execution(@com.kun.aspectjtest.aspect.DebugTrace * *..*.*(..))”)
public void DebugTraceMethod(){}
@Before(“DebugTraceMethod()”)
public void beforeDebugTraceMethod(JoinPoint joinPoint) throws Throwable{
String key = joinPoint.getSignature().toString();
Log.e(TAG, “beforeDebugTraceMethod:”+key);
}
Call
在AspectJ的切入點表達式中,我們前面都是使用的execution,實際上還有一種類型—call,那么這兩種語法有什么區別呢?對call來說:
Call (Before)
Pointcut{
Pointcut Method
}
Call (After)
對Execution來說:
Pointcut{
execution (Before)
Pointcut Method
execution (After)
}
Withincode
這個語法通常來進行一些切入點條件的過濾,作更加精確的切入控制,如下:
public class MainActivity extends AppCompatActivity{
final String TAG = MainActivity.class.getSimpleName();
@Orveride
protected void onCreate(Bundle savedInstanceState){
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
aspectJ1();
aspectJ2();
aspectJ3();
}
public void aspectJTest(){
Log.e(TAG,”execute aspectJTest");
}
public void aspectJ1(){
aspectJTest();
}
public void aspectJ2(){
aspectJTest();
}
public void aspectJ3(){
aspectJTest();
}
}
aspectJ1(),aspectJ2(),aspectJ3()都調用了aspectJTest方法,但只想在aspectJ2調用aspectJTest時插入代碼,這個時候就需要使用到Pointcut和withcode組合的方式,來精確定位切入點。
@Pointcut(“(call(* *..aspectJTest()))&&withincode(* *..aspectJ2())”)
public void invokeAspectJTestInAspectJ2(){
}
@Before(“invokeAspectJTestInAspectJ2()”)
public void beforeInvokeaspectJTestInAspectJ2(JoinPoint joinPoint) throws Throwable{
Log.e(TAG,”method:”+getMethodName(joinPoint).getName());
}
private MethodSignature getMethodName(JoinPoint joinPoint){
if(joinPoint == null) return null;
return (MethodSignature) joinPoint.getSignature();
}
execution 語法
execution()是最常用的切點函數,其語法如下所示:
例如下面這段語法:
@Around(“execution(* *..MainActivity+.on*(..))")
整個表達式可以分為五個部分:
- execution()是表達式主體
- 第一個
*
號代表返回類型,*號代表所有的類型。 - 包名 表示需要攔截的包名,這里使用*.代表匹配所有的包名。
- 第二個*號表示類名,后面跟.MainActivity是指具體的類名叫MainActivity。
*(..)
最后這個星號表示方法名,+.代表具體的函數名,*
號通配符,包括括弧號里面表示方法的參數,兩個dot代表任意參數。
遇到的錯誤
- 以下錯誤可以使用gradle2.2.3解決,由於目前還不適配gradle3.0導致的
Error:Execution failed for task ':app:transformClassesWithDexBuilderForDebug'.
> Unexpected scopes found in folder '/Users/ram/WorkSpace/AndroidWorkSpace/MyDemo/app/build/intermediates/transforms/AspectTransform/debug'. Required: PROJECT, SUB_PROJECTS, EXTERNAL_LIBRARIES. Found: EXTERNAL_LIBRARIES, PROJECT, PROJECT_LOCAL_DEPS, SUB_PROJECTS, SUB_PROJECTS_LOCAL_DEPS