開發工具: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上的更新數據:
安卓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上,這個插件也得到了推薦。
那么下面,就了解一下這個插件的使用:
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
使用過程中發現如下問題:
- 在對 android.app.Activity.onResume() 方法織入代碼時,如果其子類中沒有重寫這個方法,那么是無法織入代碼的。(也就是說,即使在onResume中不做任何操作,同樣要override)。前文中,我一直默認使用 execution,但是如果使用 call,那么即使你重寫了 onResume 也無法織入代碼,由于織入代碼的位置問題,必須要在代碼中調用了這個方法,才能成功織入。
- 在對 com.xxx.xx.BaseActivity.onResume() 方法織入代碼時,如果 BaseActivity 和其子類都重寫了 onResume() 方法,那么織入的代碼會被調用兩次。
- 對組合的自定義 pointcut 進行代碼織入的時候,joinPoint.getArgs() 獲取的參數是范圍較小的方法的參數。
- 組合自定義 pointcut 的時候,withincode + execution 無法成功織入代碼。
5.無法切割對象,所有的代碼織入都必須在界面的方法中。
如果有新發現,歡迎戳我戳我戳我! 謝謝