AspectJ in Android (一),AspectJ 基礎概念(轉)

AspectJ in Android 系列:

AspectJ in Android (一),AspectJ 基礎概念

AspectJ in Android (二),AspectJ 語法

AspectJ in Android (三),AspectJ 兩種用法以及常見問題

最近項目在做無埋點統計,解決新增功能都需要人工添加埋點的問題,其中使用了 AspectJ 技術。在這過程中發現網上關于 AspectJ 在 Android 中應用的文章比較少,所以自己整理寫了 《AspectJ in Android》系列文章,記錄自己所學的同時希望讀者可以少走一些彎路。

這篇文章主要講 AspectJ 涉及到的 AOP 編程思想和 AspectJ 的一些基本概念。

AOP

什么是 AOP ?

AOP (Aspect-Oriented Programming),面向切面編程,是一種編程思想。AOP 以切面(aspect)為基礎,切面是一種新的模塊化機制,用來描述分散在對象、類或函數中的橫切關點(crosscutting concern)。

AOP 和我們熟悉的 OOP 面向對象編程只有一字之差,面向對象編程是以對象為基礎,把功能抽象成對象模型,優先從對象的屬性和行為職責出發,把代碼分散到一個個的類中,降低代碼的復雜度同時提供復用性。但是在分散代碼時會出現重復代碼,例如,在兩個類的每個方法中加上日志。按照 OOP 的思想,需要在方法中都加入重復的日志代碼,也許有人說把日志代碼抽象成例外一個日志的類,但是這樣的話日志類和兩個類都有耦合,而且日志類和這兩個的職責沒有關聯,調用日志類的方法卻遍布兩個類的方法中。

而 AOP 的思想是把對象的核心職責外的通用邏輯(如日志,性能,校驗等)抽象出來,把散布在多個對象多個模塊的通用邏輯當作切面,然后動態地把代碼插入到類的指定方法、指定位置中,實現 AOP 的核心技術也是代碼織入技術,如 AspectJ、Javassist、DexMaker、ASMDex、動態代理等。

所以 AOP 可以說是對 OOP 的補充,OOP 以核心職責為主從縱向劃分出一個一個類,AOP 以多個類中通用邏輯為主橫向劃分出一個一個切面,AOP 讓 OOP 立體,去除重復代碼,降低耦合并且增加可維護性。

AOP 的主要功能

AOP 是以非核心職責的通用邏輯為主的,所以主要功能是把日志記錄、性能統計、安全控制、事務處理、異常處理等代碼從業務邏輯代碼中劃分出來,后面再動態織入到業務邏輯中。所以 AOP 主要用于和業務邏輯相關的通用邏輯:日志記錄、性能統計、安全控制、事務處理、異常處理等等。

AOP 概念

Aspect :切面,一個關注點的模塊化,這個關注點可能會橫切多個對象。

Join Point :連接點,程序中可切入的點,例如方法調用時、讀取某個變量時。

Pointcut :切入點,代碼注入的位置,其實就是有條件限定的 Join Point,例如只在特定方法中注入代碼。

Advice :在切入點注入的代碼,一般有 before、after、around 三種類型。

Target Object :被一個或多個 aspect 橫切攔截操作的目標對象。

Weaving : 把 Advice 代碼織入到目標對象的過程。

Inter-type declarations : 用來給一個類型聲明額外的方法或屬性。

AspectJ

AspectJ 是使用最為廣泛的 AOP 實現方案,適用于 Java 平臺,官網地址:http://www.eclipse.org/aspectj/ 。AspectJ 是在靜態織入代碼,即在編譯期注入代碼的。

AspectJ 提供了一套全新的語法實現,完全兼容 Java(跟 Java 之間的區別,只是多了一些關鍵詞而已)。同時,還提供了純 Java 語言的實現,通過注解的方式,完成代碼編織的功能。因此我們在使用 AspectJ 的時候有以下兩種方式:

  • 使用 AspectJ 的語言進行開發

  • 通過 AspectJ 提供的注解在 Java 語言上開發

因為最終的目的其實都是需要在字節碼文件中織入我們自己定義的切面代碼,不管使用哪種方式接入 AspectJ,都需要使用 AspectJ 提供的代碼編譯工具 ajc 進行編譯。

在 Android Studio 上一般使用注解的方式使用 AspectJ,因為 Android Studio 沒有 AspectJ 插件,無法識別 AspectJ 的語法(不過在 Intellij IDEA 收費版上可以使用 AspectJ 插件),所以后面的語法說明和示例都是以注解的實現方式。

引入 AspectJ

在 Android 上集成 AspectJ 比較麻煩,推薦使用 Github 上開源的 Gradle 插件 -- Android Aspectjx。該插件是利用 Gradle 的 Transform API 在項目 class 文件打包成 dex 之前進入代碼織入。

首先在項目根目錄的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 代碼單獨放到一個 library module 的話,library module 還需要添加 compile 'org.aspectj:aspectjrt:1.8.+' 依賴。

下面是使用 android-aspectjx 插件需要注意的點:

  1. android-aspectjx 插件是 使用在 application module 的插件,只能在編譯 application module 的過程中織入代碼。

  2. AspectJ 的原理是在編譯期注入代碼,所以切面只能是項目代碼、依賴的 jar 或 aar,不能注入 Android 平臺 android.jar。例如,可以在 support 包的 Fragment 中注入代碼,但是無法在 Activity 中注入代碼,只能注入項目的繼承自 Activity 的 XXActivity。

  3. android-aspectjx 默認會遍歷項目編譯后所有的 .class 文件和依賴的第三方庫去查找符合織入條件的切點,為了提升效率,可以加入過濾條件,具體見 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 動態織入后的類會在/build/intermediates/transforms/AspectTransform/debug/jars/1/1f/aspected.jar 。

反編譯后會發現 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;
    }

    ...
}

可以發現在兩個方法前加上了打印 log 的方法,這正是 AspectJ 在日志記錄上的一個簡單示例。

這篇文章就到這里,下篇文章詳細地介紹 AspectJ 的語法。

作者:JohnnyShieh
鏈接:http://www.lxweimin.com/p/9425be43968a
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,603評論 25 707
  • AspectJ in Android 系列: AspectJ in Android (一),AspectJ 基礎概...
    JohnnyShieh閱讀 4,819評論 5 23
  • 背景:公元前772年,東周周幽王烽火戲諸侯,東周呈現四分五裂的局面。周王室勢力衰微,各國諸侯國之間開始了相互兼并的...
    淑影閱讀 242評論 1 0
  • POST是用來提交數據的。提交的數據放在HTTP請求的正文里,目的在于提交數據并用于服務器端的存儲,而不允許用戶過...
    yuzhan550閱讀 1,297評論 1 0
  • 大明大學畢業以后和大多數人一樣留在了北京,在一家小公司里做文員,第一次見到大明是我們約好周末一起去兼職,等我到的時...
    瀏白閱讀 235評論 0 0