AspectJ in Android 系列:
AspectJ in Android (一)搅幅,AspectJ 基礎概念
AspectJ in Android (二)热监,AspectJ 語法
AspectJ in Android (三),AspectJ 兩種用法以及常見問題
最近項目在做無埋點統(tǒng)計殃饿,解決新增功能都需要人工添加埋點的問題谋作,其中使用了 AspectJ 技術。在這過程中發(fā)現(xiàn)網(wǎng)上關于 AspectJ 在 Android 中應用的文章比較少乎芳,所以自己整理寫了 《AspectJ in Android》系列文章遵蚜,記錄自己所學的同時希望讀者可以少走一些彎路帖池。
這篇文章主要講 AspectJ 涉及到的 AOP 編程思想和 AspectJ 的一些基本概念。
AOP
什么是 AOP 吭净?
AOP (Aspect-Oriented Programming)睡汹,面向切面編程,是一種編程思想寂殉。AOP 以切面(aspect)為基礎囚巴,切面是一種新的模塊化機制,用來描述分散在對象友扰、類或函數(shù)中的橫切關點(crosscutting concern)文兢。
AOP 和我們熟悉的 OOP 面向對象編程只有一字之差,面向對象編程是以對象為基礎焕檬,把功能抽象成對象模型姆坚,優(yōu)先從對象的屬性和行為職責出發(fā),把代碼分散到一個個的類中实愚,降低代碼的復雜度同時提供復用性兼呵。但是在分散代碼時會出現(xiàn)重復代碼,例如腊敲,在兩個類的每個方法中加上日志击喂。按照 OOP 的思想,需要在方法中都加入重復的日志代碼碰辅,也許有人說把日志代碼抽象成例外一個日志的類懂昂,但是這樣的話日志類和兩個類都有耦合,而且日志類和這兩個的職責沒有關聯(lián)没宾,調(diào)用日志類的方法卻遍布兩個類的方法中凌彬。
而 AOP 的思想是把對象的核心職責外的通用邏輯(如日志,性能循衰,校驗等)抽象出來铲敛,把散布在多個對象多個模塊的通用邏輯當作切面,然后動態(tài)地把代碼插入到類的指定方法会钝、指定位置中伐蒋,實現(xiàn) AOP 的核心技術也是代碼織入技術,如 AspectJ迁酸、Javassist先鱼、DexMaker、ASMDex奸鬓、動態(tài)代理等焙畔。
所以 AOP 可以說是對 OOP 的補充,OOP 以核心職責為主從縱向劃分出一個一個類全蝶,AOP 以多個類中通用邏輯為主橫向劃分出一個一個切面闹蒜,AOP 讓 OOP 立體,去除重復代碼抑淫,降低耦合并且增加可維護性绷落。
AOP 的主要功能
AOP 是以非核心職責的通用邏輯為主的,所以主要功能是把日志記錄始苇、性能統(tǒng)計砌烁、安全控制、事務處理催式、異常處理等代碼從業(yè)務邏輯代碼中劃分出來函喉,后面再動態(tài)織入到業(yè)務邏輯中。所以 AOP 主要用于和業(yè)務邏輯相關的通用邏輯:日志記錄荣月、性能統(tǒng)計管呵、安全控制、事務處理哺窄、異常處理等等捐下。
AOP 概念
Aspect :切面,一個關注點的模塊化萌业,這個關注點可能會橫切多個對象坷襟。
Join Point :連接點,程序中可切入的點生年,例如方法調(diào)用時婴程、讀取某個變量時。
Pointcut :切入點抱婉,代碼注入的位置档叔,其實就是有條件限定的 Join Point,例如只在特定方法中注入代碼蒸绩。
Advice :在切入點注入的代碼蹲蒲,一般有 before、after侵贵、around 三種類型届搁。
Target Object :被一個或多個 aspect 橫切攔截操作的目標對象。
Weaving : 把 Advice 代碼織入到目標對象的過程窍育。
Inter-type declarations : 用來給一個類型聲明額外的方法或屬性卡睦。
AspectJ
AspectJ 是使用最為廣泛的 AOP 實現(xiàn)方案,適用于 Java 平臺漱抓,官網(wǎng)地址:http://www.eclipse.org/aspectj/ 表锻。AspectJ 是在靜態(tài)織入代碼,即在編譯期注入代碼的乞娄。
AspectJ 提供了一套全新的語法實現(xiàn)瞬逊,完全兼容 Java(跟 Java 之間的區(qū)別显歧,只是多了一些關鍵詞而已)。同時确镊,還提供了純 Java 語言的實現(xiàn)士骤,通過注解的方式,完成代碼編織的功能蕾域。因此我們在使用 AspectJ 的時候有以下兩種方式:
使用 AspectJ 的語言進行開發(fā)
通過 AspectJ 提供的注解在 Java 語言上開發(fā)
因為最終的目的其實都是需要在字節(jié)碼文件中織入我們自己定義的切面代碼拷肌,不管使用哪種方式接入 AspectJ,都需要使用 AspectJ 提供的代碼編譯工具 ajc 進行編譯旨巷。
在 Android Studio 上一般使用注解的方式使用 AspectJ巨缘,因為 Android Studio 沒有 AspectJ 插件,無法識別 AspectJ 的語法(不過在 Intellij IDEA 收費版上可以使用 AspectJ 插件)采呐,所以后面的語法說明和示例都是以注解的實現(xiàn)方式若锁。
引入 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 插件需要注意的點:
android-aspectjx 插件是 使用在 application module 的插件口予,只能在編譯 application module 的過程中織入代碼。
AspectJ 的原理是在編譯期注入代碼涕侈,所以切面只能是項目代碼沪停、依賴的 jar 或 aar,不能注入 Android 平臺 android.jar裳涛。例如木张,可以在 support 包的 Fragment 中注入代碼,但是無法在 Activity 中注入代碼端三,只能注入項目的繼承自 Activity 的 XXActivity舷礼。
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 動態(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 在日志記錄上的一個簡單示例怀挠。
這篇文章就到這里析蝴,下篇文章詳細地介紹 AspectJ 的語法害捕。
作者:JohnnyShieh
鏈接:http://www.reibang.com/p/9425be43968a
來源:簡書
著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權闷畸,非商業(yè)轉載請注明出處尝盼。