項目簡介
一個輕量級的AOP(Android)應用框架,囊括了最實用的AOP應用。項目地址: https://github.com/xuexiangjys/XAOP, 喜歡的話,歡迎star支持!
設計原由
在我們平時開發的過程中,一定會遇到權限申請、線程切換、數據緩存、異常捕獲、埋點和方法執行時間統計等問題。這些都是非常常見的問題,實現起來也不是很難,不過就是太麻煩了,還會讓程序多出很多重複性、模版化的代碼。
設計思路
讓我最初接觸到AOP思想的是JakeWharton的hugo,通過閱讀它的源碼之後,讓我對aspectj這項技術的動態代碼編織深深地着了迷。之後我詳細研究了aspectj相關的技術,並不斷蒐集AOP在Android上的典型應用場景,然後通過aspectj這項技術去逐一實現。最後就成就了XAOP這個庫。
解決痛點
- 解決快速點擊的問題
- 解決Android6.0以上動態權限申請的問題
- 線程自由切換的問題
- 日誌埋點問題
- 緩存問題(磁盤緩存和內存緩存)
- 異常捕獲處理
- 業務攔截(登陸驗證、有效性驗證等)
集成指南
添加Gradle依賴
1.先在項目根目錄的 build.gradle
的 repositories 添加:
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
2.再在項目根目錄的 build.gradle
的 dependencies 添加xaop插件:
buildscript {
···
dependencies {
···
classpath 'com.github.xuexiangjys.XAOP:xaop-plugin:1.1.0'
}
}
3.在項目的 build.gradle
中增加依賴並引用xaop插件
apply plugin: 'com.xuexiang.xaop' //引用xaop插件
dependencies {
···
//如果是androidx項目,使用1.1.0版本及以上
implementation 'com.github.xuexiangjys.XAOP:xaop-runtime:1.1.0'
//如果是support項目,請使用1.0.5版本
implementation 'com.github.xuexiangjys.XAOP:xaop-runtime:1.0.5'
}
4.在Application
中進行初始化
XAOP.init(this); //初始化插件
XAOP.debug(true); //日誌打印切片開啓
XAOP.setPriority(Log.INFO); //設置日誌打印的等級,默認爲0
//設置動態申請權限切片 申請權限被拒絕的事件響應監聽
XAOP.setOnPermissionDeniedListener(new PermissionUtils.OnPermissionDeniedListener() {
@Override
public void onDenied(List<string> permissionsDenied) {
//權限申請被拒絕的處理
}
});
//設置自定義攔截切片的處理攔截器
XAOP.setInterceptor(new Interceptor() {
@Override
public boolean intercept(int type, JoinPoint joinPoint) throws Throwable {
XLogger.d("正在進行攔截,攔截類型:" + type);
switch(type) {
case 1:
//做你想要的攔截
break;
case 2:
return true; //return true,直接攔截切片的執行
default:
break;
}
return false;
}
});
//設置自動捕獲異常的處理者
XAOP.setIThrowableHandler(new IThrowableHandler() {
@Override
public Object handleThrowable(String flag, Throwable throwable) {
XLogger.d("捕獲到異常,異常的flag:" + flag);
if (flag.equals(TRY_CATCH_KEY)) {
return 100;
}
return null;
}
});
兼容Kotlin語法配置
1.在項目根目錄的 build.gradle
的 dependencies 添加 aspectjx 插件:
buildscript {
···
dependencies {
···
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'
}
}
2.在項目的 build.gradle
中增加依賴並引用 aspectjx 插件
apply plugin: 'android-aspectjx' //引用aspectjx插件
aspectjx {
include '項目的applicationId'
}
詳細使用可參見 kotlin-test 項目進行使用.
混淆配置
-keep @com.xuexiang.xaop.annotation.* class * {*;}
-keep @org.aspectj.lang.annotation.* class * {*;}
-keep class * {
@com.xuexiang.xaop.annotation.* <fields>;
@org.aspectj.lang.annotation.* <fields>;
}
-keepclassmembers class * {
@com.xuexiang.xaop.annotation.* <methods>;
@org.aspectj.lang.annotation.* <methods>;
}
基礎使用
快速點擊切片
SingleClick
屬性表
屬性名 | 類型 | 默認值 | 備註 |
---|---|---|---|
value | long | 1000 | 快速點擊的間隔(ms) |
1.使用@SingleClick
標註點擊的方法。注意點擊的方法中一定要有點擊控件View作爲方法參數,否則將不起作用。
2.可以設置快速點擊的時間間隔,單位:ms。不設置的話默認是1000ms。
@SingleClick(5000)
public void handleOnClick(View v) {
XLogger.e("點擊響應!");
ToastUtil.get().toast("點擊響應!");
hello("xuexiangjys", "666666");
}
動態申請權限切片
Permission
屬性表
屬性名 | 類型 | 默認值 | 備註 |
---|---|---|---|
value | String[] | / | 需要申請權限的集合 |
1.使用@Permission
標註需要申請權限執行的方法。可設置申請一個或多個權限。
2.使用@Permission
標註的方法,在執行時會自動判斷是否需要申請權限。
@SingleClick
@Permission({PermissionConsts.CALENDAR, PermissionConsts.CAMERA, PermissionConsts.LOCATION})
private void handleRequestPermission(View v) {
}
主線程切片
1.使用@MainThread
標註需要在主線程中執行的方法。
2.使用@MainThread
標註的方法,在執行時會自動切換至主線程。
@MainThread
private void doInMainThread(View v) {
mTvHello.setText("工作在主線程");
}
IO線程切片
IOThread
屬性表
屬性名 | 類型 | 默認值 | 備註 |
---|---|---|---|
value | ThreadType | ThreadType.Fixed | 子線程的類型 |
1.使用@IOThread
標註需要在io線程中執行的方法。可設置線程池的類型ThreadType
,不設置的話默認是Fixed類型。
線程池的類型如下:
- Single:單線程池
- Fixed:多線程池
- Disk:磁盤讀寫線程池(本質上是單線程池)
- Network:網絡請求線程池(本質上是多線程池)
2.使用@IOThread
標註的方法,在執行時會自動切換至指定類型的io線程。
@IOThread(ThreadType.Single)
private String doInIOThread(View v) {
return "io線程名:" + Thread.currentThread().getName();
}
日誌打印切片
DebugLog
屬性表
屬性名 | 類型 | 默認值 | 備註 |
---|---|---|---|
priority | int | 0 | 日誌的優先級 |
1.使用@DebugLog
標註需要打印的方法和類。可設置打印的優先級,不設置的話默認優先級爲0。注意:如果打印的優先級比XAOP.setPriority
設置的優先級小的話,將不會進行打印。
2.使用@DebugLog
標註的類和方法在執行的過程中,方法名、參數、執行的時間以及結果都將會被打印。
3.可調用XAOP.setISerializer
設置打印時序列化參數對象的序列化器。
4.可調用XAOP.setLogger
設置打印的實現接口。默認提供的是突破4000限制的logcat日誌打印。
@DebugLog(priority = Log.ERROR)
private String hello(String name, String cardId) {
return "hello, " + name + "! Your CardId is " + cardId + ".";
}
內存緩存切片
MemoryCache
屬性表
屬性名 | 類型 | 默認值 | 備註 |
---|---|---|---|
value | String | "" | 內存緩存的key |
enableEmpty | boolean | true | 對於String、數組和集合等,是否允許緩存爲空 |
1.使用@MemoryCache
標註需要內存緩存的方法。可設置緩存的key,不設置的話默認key爲方法名(參數1名=參數1值|參數2名=參數2值|...)
,當然你也可以修改key的自動生成規則,你只需要調用XAOP.setICacheKeyCreator
即可。
2.標註的方法一定要有返回值,否則內存緩存切片將不起作用。
3.使用@MemoryCache
標註的方法,可自動實現緩存策略。默認使用的內存緩存是LruCache
。
4.可調用XAOP.initMemoryCache
設置內存緩存的最大數量。默認是Runtime.getRuntime().maxMemory() / 1024) / 8
@MemoryCache
private String hello(String name, String cardId) {
return "hello, " + name + "! Your CardId is " + cardId + ".";
}
磁盤緩存切片
DiskCache
屬性表
屬性名 | 類型 | 默認值 | 備註 |
---|---|---|---|
value | String | "" | 內存緩存的key |
cacheTime | long | -1 | 緩存時間【單位:s】,默認是永久有效 |
enableEmpty | boolean | true | 對於String、數組和集合等,是否允許緩存爲空 |
1.使用@DiskCache
標註需要磁盤緩存的方法。可設置緩存的key,不設置的話默認key爲方法名(參數1名=參數1值|參數2名=參數2值|...)
,當然你也可以修改key的自動生成規則,你只需要調用XAOP.setICacheKeyCreator
即可。
2.可設置磁盤緩存的有效期,單位:s。不設置的話默認永久有效。
3.標註的方法一定要有返回值,否則磁盤緩存切片將不起作用。
4.使用@DiskCache
標註的方法,可自動實現緩存策略。默認使用的磁盤緩存是JakeWharton的DiskLruCache
。
5.可調用XAOP.initDiskCache
設置磁盤緩存的屬性,包括磁盤序列化器IDiskConverter
,磁盤緩存的根目錄,磁盤緩存的最大空間等。
@DiskCache
private String hello(String name, String cardId) {
return "hello, " + name + "! Your CardId is " + cardId + ".";
}
自動捕獲異常切片
Safe
屬性表
屬性名 | 類型 | 默認值 | 備註 |
---|---|---|---|
value | String | "" | 捕獲異常的標誌 |
1.使用@Safe
標註需要進行異常捕獲的方法。可設置一個異常捕獲的標誌Flag,默認的Flag爲當前類名.方法名
。
2.調用XAOP.setIThrowableHandler
設置捕獲異常的自定義處理者,可實現對異常的彌補處理。如果不設置的話,將只打印異常的堆棧信息。
3.使用@Safe
標註的方法,可自動進行異常捕獲,並統一進行異常處理,保證方法平穩執行。
@Safe(TRY_CATCH_KEY)
private int getNumber() {
return 100 / 0;
}
自定義攔截切片
Intercept
屬性表
屬性名 | 類型 | 默認值 | 備註 |
---|---|---|---|
value | int[] | / | 攔截類型 |
1.使用@Intercept
標註需要進行攔截的方法和類。可設置申請一個或多個攔截類型。
2.如果不調用XAOP.setInterceptor
設置切片攔截的攔截器的話,自定義攔截切片將不起作用。
3.使用@Intercept
標註的類和方法,在執行時將自動調用XAOP
設置的攔截器進行攔截處理。如果攔截器處理返回true的話,該類或方法的執行將被攔截,不執行。
4.使用@Intercept
可以靈活地進行切片攔截。比如用戶登錄權限等。
@SingleClick(5000)
@DebugLog(priority = Log.ERROR)
@Intercept(3)
public void handleOnClick(View v) {
XLogger.e("點擊響應!");
ToastUtil.get().toast("點擊響應!");
hello("xuexiangjys", "666666");
}
@DebugLog(priority = Log.ERROR)
@Intercept({1,2,3})
private String hello(String name, String cardId) {
return "hello, " + name + "! Your CardId is " + cardId + ".";
}
【注意】:當有多個切片註解修飾時,一般是從上至下依次順序執行。
進階使用
登陸驗證
> 在應用中,對於部分功能,如:個人中心、錢包、收藏等需要我們驗證登錄的功能,我們都可以通過@Intercept
業務攔截切片來實現。
- 定義業務攔截類型
// 登錄校驗攔截類型
public static final int INTERCEPT_LOGIN = 10;
- 定義攔截處理邏輯
XAOP.setInterceptor(new Interceptor() {
@Override
public boolean intercept(int type, JoinPoint joinPoint) throws Throwable {
switch(type) {
case INTERCEPT_LOGIN:
if (!LoginActivity.sIsLogined) { //沒登錄,進行攔截
ToastUtils.toast("請先進行登陸!");
ActivityUtils.startActivity(LoginActivity.class);
return true; //return true,直接攔截切片的執行
}
break;
default:
break;
}
return false;
}
});
- 在需要攔截的地方增加
@Intercept
標註
@Intercept(INTERCEPT_LOGIN)
public void doSomeThing() {
ToastUtils.toast("已登陸過啦~~");
}
常見問題
接入的問題
> 使用前,請一定要仔細閱讀集成指南,只要你每一步都參照文檔上寫的來接入,是不會有任何問題的!
1.問:我的項目是kotlin項目,我該怎麼使用?
答:kotlin項目的配置,只需要在原先項目的基礎上加上aspectjx 插件即可,詳情請參考兼容Kotlin語法配置 。
2.問:爲什麼我每次運行編譯時,一直報錯Invalid byte tag in constant pool
,而且會自動生成一個ajcore.xxxxxxxxx.txt
文件?
答:這裏很有可能你的項目目前還是使用的androidx版本,但是你使用的XAOP版本是support版本,導致編譯失敗。這裏需要強調的是,如果你的項目是support版本,請使用1.0.5版本;如果你的項目是androidx版本,請使用1.1.0及以上版本。
3.問:爲什麼我編譯都通過了,但是使用任何一個切片都沒有起任何作用?
答:這裏可能的原因有兩個。
- 1.你使用的XAOP版本和你的項目版本不匹配導致。比如你的項目是androidx版本,但是你卻使用XAOP的support版本,這樣瞎配的話,切片是不會起任何作用的。
- 2.你忘記在項目的
build.gradle
中增加xaop插件的引用了。
apply plugin: 'com.xuexiang.xaop' //引用xaop插件
使用的問題
1.問:爲什麼我使用@SingleClick
標註點擊的方法不起作用?
答:被@SingleClick
標註的方法中,一定要有點擊控件View作爲方法參數,否則將不起作用。
2.問:爲什麼我使用@Permission
標註的方法,返回值失效了?
答:由於動態申請權限是一個異步的操作,所以被@Permission
標註的方法是不能有返回值的。
配套設施
微信公衆號
> 更多資訊內容,歡迎微信搜索公衆號:「我的Android開源之旅」
</methods></methods></fields></fields></string>