AspectJ in Android 系列:
AspectJ in Android (一)祖很,AspectJ 基礎(chǔ)概念
AspectJ in Android (二)笛丙,AspectJ 語法
AspectJ in Android (三),AspectJ 兩種用法以及常見問題
最近項(xiàng)目在做無埋點(diǎn)統(tǒng)計突琳,解決新增功能都需要人工添加埋點(diǎn)的問題若债,其中使用了 AspectJ 技術(shù)。在這過程中發(fā)現(xiàn)網(wǎng)上關(guān)于 AspectJ 在 Android 中應(yīng)用的文章比較少拆融,所以自己整理寫了 《AspectJ in Android》系列文章蠢琳,記錄自己所學(xué)的同時希望讀者可以少走一些彎路啊终。
這篇文章主要講 AspectJ 涉及到的 AOP 編程思想和 AspectJ 的一些基本概念。
AOP
什么是 AOP 傲须?
AOP (Aspect-Oriented Programming)蓝牲,面向切面編程,是一種編程思想泰讽。AOP 以切面(aspect)為基礎(chǔ)例衍,切面是一種新的模塊化機(jī)制,用來描述分散在對象已卸、類或函數(shù)中的橫切關(guān)點(diǎn)(crosscutting concern)佛玄。
AOP 和我們熟悉的 OOP 面向?qū)ο缶幊讨挥幸蛔种睿嫦驅(qū)ο缶幊淌且詫ο鬄榛A(chǔ)累澡,把功能抽象成對象模型梦抢,優(yōu)先從對象的屬性和行為職責(zé)出發(fā),把代碼分散到一個個的類中愧哟,降低代碼的復(fù)雜度同時提供復(fù)用性奥吩。但是在分散代碼時會出現(xiàn)重復(fù)代碼,例如蕊梧,在兩個類的每個方法中加上日志霞赫。按照 OOP 的思想,需要在方法中都加入重復(fù)的日志代碼肥矢,也許有人說把日志代碼抽象成例外一個日志的類端衰,但是這樣的話日志類和兩個類都有耦合,而且日志類和這兩個的職責(zé)沒有關(guān)聯(lián)橄抹,調(diào)用日志類的方法卻遍布兩個類的方法中靴迫。
而 AOP 的思想是把對象的核心職責(zé)外的通用邏輯(如日志,性能楼誓,校驗(yàn)等)抽象出來玉锌,把散布在多個對象多個模塊的通用邏輯當(dāng)作切面,然后動態(tài)地把代碼插入到類的指定方法疟羹、指定位置中主守,實(shí)現(xiàn) AOP 的核心技術(shù)也是代碼織入技術(shù),如 AspectJ榄融、Javassist参淫、DexMaker、ASMDex愧杯、動態(tài)代理等涎才。
所以 AOP 可以說是對 OOP 的補(bǔ)充,OOP 以核心職責(zé)為主從縱向劃分出一個一個類,AOP 以多個類中通用邏輯為主橫向劃分出一個一個切面耍铜,AOP 讓 OOP 立體邑闺,去除重復(fù)代碼,降低耦合并且增加可維護(hù)性棕兼。
AOP 的主要功能
AOP 是以非核心職責(zé)的通用邏輯為主的陡舅,所以主要功能是把日志記錄、性能統(tǒng)計伴挚、安全控制靶衍、事務(wù)處理、異常處理等代碼從業(yè)務(wù)邏輯代碼中劃分出來茎芋,后面再動態(tài)織入到業(yè)務(wù)邏輯中颅眶。所以 AOP 主要用于和業(yè)務(wù)邏輯相關(guān)的通用邏輯:日志記錄、性能統(tǒng)計败徊、安全控制帚呼、事務(wù)處理、異常處理等等皱蹦。
AOP 概念
Aspect :切面,一個關(guān)注點(diǎn)的模塊化眷蜈,這個關(guān)注點(diǎn)可能會橫切多個對象沪哺。
Join Point :連接點(diǎn),程序中可切入的點(diǎn)酌儒,例如方法調(diào)用時辜妓、讀取某個變量時。
Pointcut :切入點(diǎn)忌怎,代碼注入的位置籍滴,其實(shí)就是有條件限定的 Join Point,例如只在特定方法中注入代碼榴啸。
Advice :在切入點(diǎn)注入的代碼孽惰,一般有 before、after鸥印、around 三種類型勋功。
Target Object :被一個或多個 aspect 橫切攔截操作的目標(biāo)對象。
Weaving : 把 Advice 代碼織入到目標(biāo)對象的過程库说。
Inter-type declarations : 用來給一個類型聲明額外的方法或?qū)傩浴?/p>
AspectJ
AspectJ 是使用最為廣泛的 AOP 實(shí)現(xiàn)方案狂鞋,適用于 Java 平臺,官網(wǎng)地址:http://www.eclipse.org/aspectj/ 潜的。AspectJ 是在靜態(tài)織入代碼骚揍,即在編譯期注入代碼的。
AspectJ 提供了一套全新的語法實(shí)現(xiàn)啰挪,完全兼容 Java(跟 Java 之間的區(qū)別信不,只是多了一些關(guān)鍵詞而已)纤掸。同時,還提供了純 Java 語言的實(shí)現(xiàn)浑塞,通過注解的方式借跪,完成代碼編織的功能。因此我們在使用 AspectJ 的時候有以下兩種方式:
使用 AspectJ 的語言進(jìn)行開發(fā)
通過 AspectJ 提供的注解在 Java 語言上開發(fā)
因?yàn)樽罱K的目的其實(shí)都是需要在字節(jié)碼文件中織入我們自己定義的切面代碼酌壕,不管使用哪種方式接入 AspectJ掏愁,都需要使用 AspectJ 提供的代碼編譯工具 ajc 進(jìn)行編譯。
在 Android Studio 上一般使用注解的方式使用 AspectJ卵牍,因?yàn)?Android Studio 沒有 AspectJ 插件果港,無法識別 AspectJ 的語法(不過在 Intellij IDEA 收費(fèi)版上可以使用 AspectJ 插件),所以后面的語法說明和示例都是以注解的實(shí)現(xiàn)方式糊昙。
引入 AspectJ
在 Android 上集成 AspectJ 比較麻煩辛掠,推薦使用 Github 上開源的 Gradle 插件 -- Android Aspectjx。該插件是利用 Gradle 的 Transform API 在項(xiàng)目 class 文件打包成 dex 之前進(jìn)入代碼織入释牺。
首先在項(xiàng)目根目錄的build.gradle
中添加 gradle 插件依賴:
dependencies {
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.10'
}
然后在 app 模塊的 build.gradle 添加 AspectJ 的依賴:
apply plugin: 'android-aspectjx'
dependencies {
compile 'org.aspectj:aspectjrt:1.8.10'
}
如果把 AspectJ 代碼單獨(dú)放到一個 library module 的話萝衩,library module 還需要添加 compile 'org.aspectj:aspectjrt:1.8.+'
依賴。
下面是使用 android-aspectjx 插件需要注意的點(diǎn):
android-aspectjx 插件是 使用在 application module 的插件没咙,只能在編譯 application module 的過程中織入代碼猩谊。
AspectJ 的原理是在編譯期注入代碼,所以切面只能是項(xiàng)目代碼祭刚、依賴的 jar 或 aar牌捷,不能注入 Android 平臺 android.jar。例如涡驮,可以在 support 包的 Fragment 中注入代碼暗甥,但是無法在 Activity 中注入代碼,只能注入項(xiàng)目的繼承自 Activity 的 XXActivity捉捅。
android-aspectjx 默認(rèn)會遍歷項(xiàng)目編譯后所有的 .class 文件和依賴的第三方庫去查找符合織入條件的切點(diǎn)撤防,為了提升效率,可以加入過濾條件锯梁,具體見 Android Aspectjx 的文檔即碗。
AspectJ 示例
下面先不考慮 AspectJ 的語法,先看下簡單使用 AspectJ 的示例陌凳。在 Fragment 的 onResume 和 onPause 時打印 log 信息剥懒,新建一個 FragmentAspect 類:
public class FragmentAspect {
private static final String TAG = "FragmentAspect";
@Pointcut("execution(void android.support.v4.app.Fragment.onResume()) && target(fragment)")
public void onResume(Fragment fragment) {}
@Pointcut("execution(void android.support.v4.app.Fragment.onPause()) && target(fragment)")
public void onPause(Fragment fragment) {}
@Before("onResume(fragment)")
public void beforeOnResume(Fragment fragment) {
Log.d(TAG, fragment.getClass().getSimpleName() + " onResume");
}
@Before("onPause(fragment)")
public void beforeOnPause(Fragment fragment) {
Log.d(TAG, fragment.getClass().getSimpleName() + " onPause");
}
}
這樣編譯后就可以修改在 support-v4 包中的 Fragment 類文件,通過 AspectJ 動態(tài)織入后的類會在/build/intermediates/transforms/AspectTransform/debug/jars/1/1f/aspected.jar 合敦。
反編譯后會發(fā)現(xiàn) jar 包中有 Fragment 的代碼如下:
public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener {
...
@CallSuper
public void onResume() {
FragmentAspect.aspectOf().beforeOnResume(this);
this.mCalled = true;
}
...
@CallSuper
public void onPause() {
FragmentAspect.aspectOf().beforeOnPause(this);
this.mCalled = true;
}
...
}
可以發(fā)現(xiàn)在兩個方法前加上了打印 log 的方法初橘,這正是 AspectJ 在日志記錄上的一個簡單示例。
這篇文章就到這里,下篇文章詳細(xì)地介紹 AspectJ 的語法保檐。