小菜在嘗試 Android 性能優化過程中,需要統計的監測各個方法執行調用時間,以及對應 Systrace 生成時;較爲優雅的方式就是採用 AOP 切片模式,而 AOP 模式中較爲成熟和簡單的當屬 AspectJ;小菜進行簡單集成與測試;
AspectJ
基本簡介
AOP(Aspect Oriented Programming) 是一種面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術;可以通過 AOP 對業務邏輯進行整體的切面拆分,卻又不影響業務邏輯,提高了開發效率和可重用性;
AspectJ 適用於 Java 平臺,是使用較爲廣泛的 AOP 切面方案;提供了純 Java 語言實現,通過註解的方式,在編譯期進行代碼注入;即在編譯階段,就對目標類進行修改,得到的 .class 文件已經是修改過的,生成靜態的 AOP 代理類;小菜剛瞭解 AspectJ,需要了解幾個最基本的概念;
1. Aspect
Aspect 是 AOP 中的切面文件,一般將需要在註解開發的 Java 類文件頂部註明 @Aspect,不能修飾接口;
2. JoinPoint
JoinPoint 作爲連接點,是程序運行時的一些執行點;例如方法調用時,或讀寫變量以及異常處理等;官網 介紹非常詳細,小菜提醒注意 call() & execution() 這兩個方法;
小菜瞭解 call() & execution() 織入對象不同,call() 是在指定方法被調用時調用,而 execution() 是在方法執行內部時被調用;
3. Pointcut
Pointcut 是具體的切入點,目標是提供一種方法,使開發者可以選擇感興趣的 JoinPoint;
通配符 | 說明 |
---|---|
* | 匹配任何數量字符 |
.. | 匹配任何數量字符的重複,如在類型模式中匹配任何數量子包;而在方法參數模式中匹配任何數量參數 |
+ | 匹配指定類型的子類型;僅能作爲後綴放在類型模式後邊 |
4. Advice
Advice 用於邏輯處理時切面功能的實現;註解修飾的方法需爲 public,Around 使用的是 ProceedingJoinPoint,其他的是 JoinPoint;
Advice | 說明 | 返回類型 |
---|---|---|
@Before | 執行 JoinPoint 之前 | 必須爲 void |
@After | 執行 JoinPoint 之後,包括正常的 return 和 throw 異常 | 必須爲 void |
@AfterReturning | JoinPoint 爲方法調用且正常 return 時,不指定返回類型時匹配所有類型 | 必須爲 void |
@AfterThrowing | JoinPoint 爲方法調用且拋出異常時,不指定異常類型時匹配所有類型 | 必須爲 void |
@Around | 執行 JoinPoint 之前和之後 | -- |
集成測試
AspectJ 的集成非常簡單,僅需簡單的幾個步驟:
1. 根目錄 dependencies 中引入插件;
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.5'
2. 在應用 module 中應用插件;
apply plugin: 'android-aspectjx'
3. 編輯記錄時間切片的 AOP 文件;
@Aspect
public class TimeAOP {
@Around("call(* com.package.name.Application.**(..))")
public void getApplicationTime(ProceedingJoinPoint joinPoint) {
long time = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.e("TimeAOP:", "Application " + joinPoint.getSignature().getName() + " Time = " + (System.currentTimeMillis() - time));
}
@Pointcut("execution(* com.package.name.MainActivity.**(..))")
public void callMethod() {
}
@Around("callMethod()")
public void getActivityTime(ProceedingJoinPoint joinPoint) {
long time = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.e("TimeAOP:", "Activity " + joinPoint.getSignature().getName() + " Time = " + (System.currentTimeMillis() - time));
}
}
4. 在應用 module 中配置排除衝突的 aspectjx
android {
...
aspectjx {
enabled true
include "packagename"
exclude 'com.google','com.appsflyer','com.android' // 可根據具體的業務確定是否排除
}
}
5. 在應用 module 中添加,此步驟可省略
// 不是必須的,但是爲了有時候去掉上面插件不報錯就需要增加
api 'org.aspectj:aspectjrt:1.9.4'
問題 & 修復
AspectJ 的集成只有簡單的幾步,但小菜卻折騰了很久;小菜先在 Demo 中集成測試,一切正常;但是應用在歷史項目中,卻坎坷頗多;
Q1:[TAG] Failed to resolve variable '${httpcore.version}'
A1:
其原因是小菜未在 module 中配置 aspectjx,添加對應的 aspectjx 配置即可;
Q2:Cause: zip file is empty
A2:
根據問題,小菜以爲是 Gradle 版本不一致,反覆 clean / rebuild 多次後依舊不生效,同時在其他 module 中添加 'android-aspectjx',結果並不生效;最終小菜發現自己編輯的 AOP 切片文件格式有誤,少了一個 * 導致的;因此小菜建議 AOP 文件先編輯最簡單方法,逐步完善操作;
AspectJ 的功能非常強大,小菜剛學習很多切入規則還不熟悉,僅嘗試了最基本的 @Around 方式獲取方法的耗時時間;小菜建議在編輯規則過程中,多審查幾遍,防止出現因規則錯誤導致的不容易查找的崩潰;
通過這次學習,小菜深得體會,不管看似多麼簡單的應用,只有實際上手操作才能體會深刻,官網是最詳細的學習資料;如有錯誤,請多多指導!
來源: 阿策小和尚