開發(fā)工具:Android Studio
參考鏈接:
1.一個流傳廣泛到不知道哪個是原版的博客
2.基于上面內(nèi)容中第二種方式配置的具體說明
3.基于上述方法的測試代碼庫
4.一個插件 gradle-android-aspectj-plugin
5.另一個插件 gradle_plugin_android_aspectjx
6.Aspect Oriented Programming in Android
做安卓都知道 OOP 面向?qū)ο缶幊腆镆K鼘⒁幌盗惺挛锍橄蠡魑常阉麄冇泄驳膶傩约系揭黄鹂粒蔀槟芨叨雀爬骋活惥唧w事物的概念。比如交通工具是一個抽象概念,而具體實現(xiàn)則有汽車酒奶、自行車等等废麻,相信走在安卓開發(fā)路上的我們都很熟悉,但是在面向?qū)ο缶幊痰倪^程中邦马,我們遇到這樣的問題:需要對某些事件進行統(tǒng)一的處理,比如統(tǒng)計埋點宴卖、權限控制滋将、日志打印。顯然症昏,我們可以自定義一個類随闽,然后在不同事件中分別調(diào)用指定方法,這樣的思想在我們?nèi)粘5木幊讨幸呀?jīng)有了相當成熟的應用肝谭,但是終究還是不夠智能和便捷掘宪。
于是乎,偉大的 AOP 出現(xiàn)了攘烛,解決了許許多多的問題魏滚,而今天,我們就來簡單了解一下 AOP坟漱。
一鼠次、什么是AOP ?
AOP是Aspect Oriented Programming的縮寫芋齿,也就是題目里說的面相切面編程腥寇。它通過預編譯或者運行期動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護。AOP 是 OOP的延續(xù)觅捆,也是函數(shù)式編程的一種衍生泛型赦役。利用AOP可以對業(yè)務邏輯的各個部分進行隔離,從而使得業(yè)務邏輯之間的耦合度降低栅炒,提高程序的可重用性掂摔。
還不是很了解的可以去稍微看一下,有點多赢赊,簡單看看就好乙漓。百度鏈接
二、運行時AOP框架
Dexposed域携,這是是阿里巴巴無線事業(yè)部第一個重量級 Andorid 開源軟件簇秒,基于 ROOT 社區(qū)著名開源項目Xposed 改造剝離了 ROOT 部分,演化為服務于所在應用自身的 AOP 框架秀鞭。它支撐了阿里大部分 App 的在線分鐘級客戶端 bugfix 和線上調(diào)試能力趋观。
但是扛禽,它在2015年貌似就已經(jīng)停止更新了
但是,它在2015年貌似就已經(jīng)停止更新了
但是皱坛,它在2015年貌似就已經(jīng)停止更新了
看看github上的更新數(shù)據(jù):
安卓5.0使用ART虛擬機后编曼,這個庫的支持就一直停留在 testing 再也沒變過。前段時間剩辟,埋點數(shù)據(jù)和ios嚴重不符合掐场,才發(fā)現(xiàn)了這個問題,為此重新整理一下贩猎。
三熊户、預編譯AOP框架 -- AspectJ
由于找不到合適的運行時框架,我們決定使用預編譯的AOP框架 -- AspectJ吭服。根據(jù)網(wǎng)上目前的資料來看嚷堡,可以使用的主要有兩種方法:
- 1.使用插件 gradle-android-aspectj-plugin
- 2.自己配置 Gradle,添加腳本艇棕。
這種情況下蝌戒,為了快速開發(fā),一般當然會選擇先考察插件的可使用性沼琉。
(如果想看自己配置 Gradle 的方法北苟,可以直接跳到后面看 3.4 ~):-)
根據(jù)資料,gradle-android-aspectj-plugin 這個插件是不能支持 data-binding 的打瘪,雖然我公司也用不到友鼻,但是總希望能找到一個更優(yōu)的解決方案。于是乎瑟慈,我在公眾號中找到了需要的東西——根據(jù)上面的插件改良的插件桃移。并且在原作者的Github上屋匕,這個插件也得到了推薦葛碧。
那么下面,就了解一下這個插件的使用:
3.1 使用插件的走一波效果
工程- gradle
buildscript {
...
dependencies {
...
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.8'
}
}
app - gradle
apply plugin: 'android-aspectjx'
//更詳細的使用作者在readme中已經(jīng)舉例寫得很明白了过吻,可以自行參考上面的鏈接
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 后,打開控制臺逼纸,你就會看到我們對應的日志打印洋措。這是最簡單的實現(xiàn),我們可以看到杰刽,在沒有更改 MainActivity 中代碼的情況下菠发,我們成功截取了 onCreate 方法的調(diào)用王滤,并在它調(diào)用之前打印了日志。
那么下面讓我們看看使用 AspectJ 需要了解哪些元素滓鸠。
3.2 AspectJ 相關詞匯了解
- 橫向切面:傳統(tǒng)的面相對象編程雁乡,每個單元就是一個類,在單一功能上可以很好地實現(xiàn)糜俗,但是他們通常會和其他類共同或者相關聯(lián)的需求踱稍。比如日志、埋點等悠抹,盡管每個主類都有不同的功能珠月,但是在細節(jié)功能需求上,會出現(xiàn)很多相同的代碼楔敌。這就是我們 AspectJ 的功能桥温,將不同的類橫向切面,將某些需要添加相同代碼的切點集合到一起做處理梁丘,減少代碼的冗余和類之間的耦合侵浸。
- Advice:注入到類文件當中的代碼以及我們?nèi)绾巫⑷脒@些代碼。前面例子中的 @Before 就是插入的方法氛谜,而日志打印就是具體的代碼掏觉。
- Join Point:程序中的一個特定切點,可能是切入代碼的目標
- Pointcut:看了下各種各樣的說法值漫,拿捏不準澳腹。但是根據(jù)用的時候的感受來看,應該是具體定義的 Join Point杨何,是特殊的Join Point酱塔。
- Aspect:Aspect 是 Pointcut 和 advice 的結合
- Weaving:織入代碼的過程
3.3 Advice - Before && After && Around
以上三種切入方式是最常用的,切入方式如英文單詞的意思危虱,非常好理解羊娃。我們以下面的代碼為例,說明調(diào)用過程埃跷。
@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);
}
// -- 執(zhí)行順序
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);
}
// -- 執(zhí)行順序
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);
}
// -- 執(zhí)行順序
Log.d(TAG,msg)
MainActivity.onCreate(.)
Log.d(TAG,msg2)
3.4 自己配置 Gradle 插件蕊玷,添加腳本
根據(jù)博客的說法,我照搬了一套弥雹,放到我的 Gradle 文件中去垃帅,然后報紅,以下文件無法導入剪勿。
import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
之后我升級了一下 Gradle 版本贸诚,編譯成功。但是配置了一些切入點,卻并不執(zhí)行相關內(nèi)容酱固。于是上網(wǎng)找了些資料二鳄,發(fā)現(xiàn)不少人和我一樣對著這個英文資料在配置 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 上又有新發(fā)現(xiàn)定踱,但是鏈接已經(jīng)找不到了棍潘,總之就是不能用 = = ,白叨叨了我好久崖媚,有點費時間亦歉。
所以,乖乖用插件吧畅哑。
-------------------------------------- 分割線 ------------------------------------------------
07/26
使用過程中發(fā)現(xiàn)如下問題:
- 在對 android.app.Activity.onResume() 方法織入代碼時肴楷,如果其子類中沒有重寫這個方法,那么是無法織入代碼的荠呐。(也就是說赛蔫,即使在onResume中不做任何操作,同樣要override)泥张。前文中呵恢,我一直默認使用 execution,但是如果使用 call媚创,那么即使你重寫了 onResume 也無法織入代碼渗钉,由于織入代碼的位置問題,必須要在代碼中調(diào)用了這個方法筝野,才能成功織入晌姚。
- 在對 com.xxx.xx.BaseActivity.onResume() 方法織入代碼時粤剧,如果 BaseActivity 和其子類都重寫了 onResume() 方法歇竟,那么織入的代碼會被調(diào)用兩次。
- 對組合的自定義 pointcut 進行代碼織入的時候抵恋,joinPoint.getArgs() 獲取的參數(shù)是范圍較小的方法的參數(shù)焕议。
- 組合自定義 pointcut 的時候,withincode + execution 無法成功織入代碼弧关。
5.無法切割對象盅安,所有的代碼織入都必須在界面的方法中唤锉。
如果有新發(fā)現(xiàn),歡迎戳我戳我戳我别瞭! 謝謝