在Android項(xiàng)目中使用AspectJ的方法
什么是AOP
AOP是 Aspect Oriented Programming 的縮寫,即面向切面編程,和平常遇到的面向?qū)ο驩OP編程不一樣的是,OOP是將功能模塊化對(duì)象化,AOP是針對(duì)同一類的問(wèn)題統(tǒng)一化處理。例如做日志埋點(diǎn),性能監(jiān)控,動(dòng)態(tài)權(quán)限控制等。
AspectJ
AspectJ實(shí)際上是對(duì)AOP編程的實(shí)踐,目前還有很多的AOP實(shí)現(xiàn),如ASMDex,但筆者選用的是AspectJ。
在Android項(xiàng)目中使用AspectJ
如果使用原生AspectJ在項(xiàng)目中配置會(huì)非常麻煩,在GitHub上有個(gè)開(kāi)源的SDK gradle_plugin_android_aspectjx基于gradle配置即可。
接入說(shuō)明
請(qǐng)自行查看開(kāi)源項(xiàng)目中的接入配置過(guò)程
AspectJ 之 Join Points介紹
Join Points在AspectJ中是關(guān)鍵的概念。Join Points可以看做是程序運(yùn)行時(shí)的一個(gè)執(zhí)行點(diǎn),比如:一個(gè)函數(shù)的調(diào)用可以看做是個(gè)Join Points,相當(dāng)于代碼切入點(diǎn)。但在AspectJ中,只有下面幾種執(zhí)行點(diǎn)是認(rèn)為是Join Points:
Join Points | 說(shuō)明 | 實(shí)例 |
---|---|---|
method call | 函數(shù)調(diào)用 | 比如調(diào)用Log.e(),這是一個(gè)個(gè)Join Point |
method execution | 函數(shù)執(zhí)行 | 比如Log.e()的執(zhí)行內(nèi)部,是一處Join Points。注意這里是函數(shù)內(nèi)部 |
constructor call | 構(gòu)造函數(shù)調(diào)用 | 和method call 類似 |
constructor execution | 構(gòu)造函數(shù)執(zhí)行 | 和method execution 類似 |
field get | 獲取某個(gè)變量 | 比如讀取DemoActivity.debug成員 |
field set | 設(shè)置某個(gè)變量 | 比如設(shè)置DemoActivity.debug成員 |
pre-initialization | Object在構(gòu)造函數(shù)中做的一些工作。 | - |
initialization | Object在構(gòu)造函數(shù)中做的工作。 | - |
static initialization | 類初始化 | 比如類的static{} |
handler | 異常處理 | 比如try catch 中,對(duì)應(yīng)catch內(nèi)的執(zhí)行 |
advice execution | 這個(gè)是AspectJ 的內(nèi)容 | - |
Pointcuts 介紹
一個(gè)程序會(huì)有多個(gè)Join Points,即使同一個(gè)函數(shù),也還分為call 和 execution 類型的Join Points,但并不是所有的Join Points 都是我們關(guān)心的,Pointcuts 就是提供一種使得開(kāi)發(fā)者能夠值選擇所需的JoinPoints的方法。
Advice
Advice就是我們插入的代碼可以以何種方式插入,有Before 還有 After、Around。
下面看個(gè)例子:
@Before(“execution(* android.app.Activity.on**(..)))”) public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable{ }
這里會(huì)分成好幾個(gè)部分,我們依次來(lái)看:
- @Before: Advice, 也就是具體的插入點(diǎn)
- execution:處理Join Point的類型,例如call、execution
- (* android.app.Activity.on**(..)): 這個(gè)是最重要的表達(dá)式,第一個(gè)*表示返回值,*表示返回值為任意類型,后面這個(gè)就是典型的包名路徑,其中可以包含 *來(lái)進(jìn)行通配,幾個(gè) *沒(méi)有區(qū)別。同時(shí)這里可以通過(guò)&&、||、!來(lái)進(jìn)行條件組合。()代表這個(gè)方法的參數(shù),你可以指定類型,例如android.os.Bundle,或者 (..) 這樣來(lái)代表任意類型、任意個(gè)數(shù)的參數(shù)。
- public void onActivityMehodBefore: 實(shí)際切入的代碼。
Before 和 After 其實(shí)還是很好理解的,也就是在Pointcuts之前和之后,插入代碼,那么Android呢,從字面含義上來(lái)講,也就是在方法前后各插入代碼,他包含了 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()代表執(zhí)行原始的方法,在這之前、之后,都可以進(jìn)行各種邏輯處理。
自定義Pointcuts
自定義Pointcuts可以讓我們更加精準(zhǔn)的切入一個(gè)或多個(gè)指定的切入點(diǎn)。
首先我們要定義一個(gè)注解類
@Retention(RetentionPolicy.CLASS) @Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) public @interface DebugTrace { }
在需要插入代碼的地方加入這個(gè)注解,例如在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"); } }
最后創(chuàng)建切入代碼
@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的切入點(diǎn)表達(dá)式中,我們前面都是使用的execution,實(shí)際上還有一種類型—call,那么這兩種語(yǔ)法有什么區(qū)別呢?對(duì)call來(lái)說(shuō):
Call (Before) Pointcut{ Pointcut Method } Call (After)
對(duì)Execution來(lái)說(shuō):
Pointcut{ execution (Before) Pointcut Method execution (After) }
Withincode
這個(gè)語(yǔ)法通常來(lái)進(jìn)行一些切入點(diǎn)條件的過(guò)濾,作更加精確的切入控制,如下:
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()都調(diào)用了aspectJTest方法,但只想在aspectJ2調(diào)用aspectJTest時(shí)插入代碼,這個(gè)時(shí)候就需要使用到Pointcut和withcode組合的方式,來(lái)精確定位切入點(diǎn)。
@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 語(yǔ)法
execution()是最常用的切點(diǎn)函數(shù),其語(yǔ)法如下所示:
例如下面這段語(yǔ)法:@Around(“execution(* *..MainActivity+.on*(..))")
整個(gè)表達(dá)式可以分為五個(gè)部分:
1.execution()是表達(dá)式主體
2.第一個(gè)*號(hào)代表返回類型,*號(hào)代表所有的類型。
3.包名 表示需要攔截的包名,這里使用*.代表匹配所有的包名。
4.第二個(gè)*號(hào)表示類名,后面跟.MainActivity是指具體的類名叫MainActivity。
5.*(..) 最后這個(gè)星號(hào)表示方法名,+.代表具體的函數(shù)名,*號(hào)通配符,包括括弧號(hào)里面表示方法的參數(shù),兩個(gè)dot代表任意參數(shù)。
遇到的錯(cuò)誤
1.以下錯(cuò)誤可以使用gradle2.2.3解決,由于目前還不適配gradle3.0導(dǎo)致的
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
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android調(diào)用系統(tǒng)時(shí)間格式顯示時(shí)間信息
這篇文章主要介紹了Android調(diào)用系統(tǒng)時(shí)間格式顯示時(shí)間信息的使用方法,代碼很簡(jiǎn)單2014-01-01Android字符串資源文件format方法使用實(shí)例
本文介紹了Android的資源文件values/strings.xml中如何實(shí)現(xiàn)格式化字符串,這里舉個(gè)簡(jiǎn)單的例子供大家參考2013-11-11Android自定義控件實(shí)現(xiàn)icon+文字的多種效果
這篇文章主要為大家詳細(xì)介紹了Android自定義控件實(shí)現(xiàn)icon+文字的多種效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01android 加載本地聯(lián)系人實(shí)現(xiàn)方法
在android開(kāi)發(fā)過(guò)程中,有些功能需要訪問(wèn)本地聯(lián)系人列表,本人搜集整理了一番,拿出來(lái)和大家分享一下,希望可以幫助你們2012-12-12Jetpack?Compose重寫TopAppBar實(shí)現(xiàn)標(biāo)題多行折疊詳解
這篇文章主要為大家介紹了Jetpack?Compose重寫TopAppBar實(shí)現(xiàn)標(biāo)題多行折疊示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Android編程解析XML文件的方法詳解【基于XmlPullParser】
這篇文章主要介紹了Android編程解析XML文件的方法,結(jié)合實(shí)例形式分析了Android基于XmlPullParser解析xml文件的相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2017-07-07Android APP檢測(cè)實(shí)體按鍵事件詳解
這篇文章主要為大家詳細(xì)介紹了Android APP檢測(cè)實(shí)體按鍵事件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08Android利用GridView實(shí)現(xiàn)單選功能
這篇文章主要為大家詳細(xì)介紹了Android利用GridView實(shí)現(xiàn)單選功能的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02Android BLE設(shè)置MTU大小實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Android BLE設(shè)置MTU大小實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04