AspectJ In Android Studio

開發工具:Android Studio
參考鏈接:
1.一個流傳廣泛到不知道哪個是原版的博客
2.基于上面內容中第二種方式配置的具體說明
3.基于上述方法的測試代碼庫
4.一個插件 gradle-android-aspectj-plugin
5.另一個插件 gradle_plugin_android_aspectjx
6.Aspect Oriented Programming in Android


做安卓都知道 OOP 面向對象編程。它將一系列事物抽象化,把他們有公共的屬性集合到一起,成為能高度概括某一類具體事物的概念。比如交通工具是一個抽象概念,而具體實現則有汽車、自行車等等,相信走在安卓開發路上的我們都很熟悉,但是在面向對象編程的過程中,我們遇到這樣的問題:需要對某些事件進行統一的處理,比如統計埋點、權限控制、日志打印。顯然,我們可以自定義一個類,然后在不同事件中分別調用指定方法,這樣的思想在我們日常的編程中已經有了相當成熟的應用,但是終究還是不夠智能和便捷。
于是乎,偉大的 AOP 出現了,解決了許許多多的問題,而今天,我們就來簡單了解一下 AOP。

一、什么是AOP ?

AOP是Aspect Oriented Programming的縮寫,也就是題目里說的面相切面編程。它通過預編譯或者運行期動態代理實現程序功能的統一維護。AOP 是 OOP的延續,也是函數式編程的一種衍生泛型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯之間的耦合度降低,提高程序的可重用性。
還不是很了解的可以去稍微看一下,有點多,簡單看看就好。百度鏈接

二、運行時AOP框架

Dexposed,這是是阿里巴巴無線事業部第一個重量級 Andorid 開源軟件,基于 ROOT 社區著名開源項目Xposed 改造剝離了 ROOT 部分,演化為服務于所在應用自身的 AOP 框架。它支撐了阿里大部分 App 的在線分鐘級客戶端 bugfix 和線上調試能力。
但是,它在2015年貌似就已經停止更新了
但是,它在2015年貌似就已經停止更新了
但是,它在2015年貌似就已經停止更新了
看看github上的更新數據:

[alibaba](https://github.com/alibaba)/[dexposed](https://github.com/alibaba/dexposed)截圖

安卓5.0使用ART虛擬機后,這個庫的支持就一直停留在 testing 再也沒變過。前段時間,埋點數據和ios嚴重不符合,才發現了這個問題,為此重新整理一下。

三、預編譯AOP框架 -- AspectJ

由于找不到合適的運行時框架,我們決定使用預編譯的AOP框架 -- AspectJ。根據網上目前的資料來看,可以使用的主要有兩種方法:

  • 1.使用插件 gradle-android-aspectj-plugin
  • 2.自己配置 Gradle,添加腳本。

這種情況下,為了快速開發,一般當然會選擇先考察插件的可使用性。
(如果想看自己配置 Gradle 的方法,可以直接跳到后面看 3.4 ~):-)
根據資料,gradle-android-aspectj-plugin 這個插件是不能支持 data-binding 的,雖然我公司也用不到,但是總希望能找到一個更優的解決方案。于是乎,我在公眾號中找到了需要的東西——根據上面的插件改良的插件。并且在原作者的Github上,這個插件也得到了推薦。

gradle-android-aspectj-plugin 首頁

那么下面,就了解一下這個插件的使用:

3.1 使用插件的走一波效果

工程- gradle

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.8'
    }
}

app - gradle

apply plugin: 'android-aspectjx'
//更詳細的使用作者在readme中已經舉例寫得很明白了,可以自行參考上面的鏈接
aspectjx {
    //加入需要織入代碼的第三方庫
    includeJarFilter '...xxx', '...xxx'
    
    //排除不需要織入代碼的第三方庫
    excludeJarFilter '...xxx'
}
dependencies{
        ...
        compile 'org.aspectj:aspectjrt:1.8.9'
}

AspectTest

@Aspect
public class AspectTest {

    private static final String TAG = "AspectTest";
    // 復制代碼需要注意修改下面的 com.arno.testaspectj 為你的包名
    private static final String ACTI_ONCREATE = "execution(com.arno.testaspectj.MainActivity.onCreate(..))";
    
    @Before(ACTI_ONCREATE )
    public void onActivityCreate(JoinPoint joinPoint) throws Throwable {
        Log.d(TAG, "onActivityCreate: " +  joinPoint.getSignature().toString());
    }
   
}

主界面什么也沒動,這樣工程就應該可以跑起來了。
If you meet any problem,I recommend strongly to update your gradle first,clean your project and run it.
在進入 MainActivity 后,打開控制臺,你就會看到我們對應的日志打印。這是最簡單的實現,我們可以看到,在沒有更改 MainActivity 中代碼的情況下,我們成功截取了 onCreate 方法的調用,并在它調用之前打印了日志。
那么下面讓我們看看使用 AspectJ 需要了解哪些元素。

3.2 AspectJ 相關詞匯了解
  • 橫向切面:傳統的面相對象編程,每個單元就是一個類,在單一功能上可以很好地實現,但是他們通常會和其他類共同或者相關聯的需求。比如日志、埋點等,盡管每個主類都有不同的功能,但是在細節功能需求上,會出現很多相同的代碼。這就是我們 AspectJ 的功能,將不同的類橫向切面,將某些需要添加相同代碼的切點集合到一起做處理,減少代碼的冗余和類之間的耦合。
  • Advice:注入到類文件當中的代碼以及我們如何注入這些代碼。前面例子中的 @Before 就是插入的方法,而日志打印就是具體的代碼。
  • Join Point:程序中的一個特定切點,可能是切入代碼的目標
  • Pointcut:看了下各種各樣的說法,拿捏不準。但是根據用的時候的感受來看,應該是具體定義的 Join Point,是特殊的Join Point。
  • Aspect:Aspect 是 Pointcut 和 advice 的結合
  • Weaving:織入代碼的過程
3.3 Advice - Before && After && Around

以上三種切入方式是最常用的,切入方式如英文單詞的意思,非常好理解。我們以下面的代碼為例,說明調用過程。

@xxx注解("execution(com.arno.testaspectj.MainActivity.onCreate(..))")
public void onActivityCreate(JoinPoint joinPoint) throws Throwable {
    Log.d(TAG, msg);
}
  • Before 注解效果:
@Before ("execution(com.arno.testaspectj.MainActivity.onCreate(..))")
public void onActivityCreate(JoinPoint joinPoint) throws Throwable {
    Log.d(TAG, msg);
}
// -- 執行順序
Log.d(TAG,msg)
MainActivity.onCreate(.)
  • After 注解效果:
@After ("execution(com.arno.testaspectj.MainActivity.onCreate(..))")
public void onActivityCreate(JoinPoint joinPoint) throws Throwable {
    Log.d(TAG, msg);
}
// -- 執行順序
MainActivity.onCreate(.)
Log.d(TAG,msg)
  • Around 注解效果:
@After ("execution(com.arno.testaspectj.MainActivity.onCreate(..))")
public void onActivityCreate(ProceedingJoinPoint proceedingJoinPoint ) throws Throwable {
    Log.d(TAG, msg);
    proceedingJoinPoint.proceed();
    Log.d(TAG,msg2);
}
// -- 執行順序
Log.d(TAG,msg)
MainActivity.onCreate(.)
Log.d(TAG,msg2)
3.4 自己配置 Gradle 插件,添加腳本

根據博客的說法,我照搬了一套,放到我的 Gradle 文件中去,然后報紅,以下文件無法導入。

import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

之后我升級了一下 Gradle 版本,編譯成功。但是配置了一些切入點,卻并不執行相關內容。于是上網找了些資料,發現不少人和我一樣對著這個英文資料在配置 aspectj 。有人說,配置中的 java 版本改一下就可以了。

String[] args = ["-showWeaveInfo",
                         "-1.7", // <-- java 版本
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", plugin.project.android.bootClasspath.join(
                File.pathSeparator)
        ]

然而沒有用,這期間,我試了各種方法,包括但不僅限于:clean project,rebuild project,new project... 后來我在 stackoverflow 上找到了一個答案:不靠譜的鏈接
按照它做了,然而。呵呵 :-(
后面在 stackoverflow 上又有新發現,但是鏈接已經找不到了,總之就是不能用 = = ,白叨叨了我好久,有點費時間。
所以,乖乖用插件吧。

-------------------------------------- 分割線 ------------------------------------------------

07/26
使用過程中發現如下問題:

  1. 在對 android.app.Activity.onResume() 方法織入代碼時,如果其子類中沒有重寫這個方法,那么是無法織入代碼的。(也就是說,即使在onResume中不做任何操作,同樣要override)。前文中,我一直默認使用 execution,但是如果使用 call,那么即使你重寫了 onResume 也無法織入代碼,由于織入代碼的位置問題,必須要在代碼中調用了這個方法,才能成功織入。
  2. 在對 com.xxx.xx.BaseActivity.onResume() 方法織入代碼時,如果 BaseActivity 和其子類都重寫了 onResume() 方法,那么織入的代碼會被調用兩次。
  3. 對組合的自定義 pointcut 進行代碼織入的時候,joinPoint.getArgs() 獲取的參數是范圍較小的方法的參數。
  4. 組合自定義 pointcut 的時候,withincode + execution 無法成功織入代碼。
    5.無法切割對象,所有的代碼織入都必須在界面的方法中。

如果有新發現,歡迎戳我戳我戳我! 謝謝

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

推薦閱讀更多精彩內容