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
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

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