項(xiàng)目簡介
一個輕量級的AOP(Android)應(yīng)用框架,囊括了最實(shí)用的AOP應(yīng)用事镣。項(xiàng)目地址: https://github.com/xuexiangjys/XAOP, 喜歡的話,歡迎star支持!
設(shè)計原由
在我們平時開發(fā)的過程中婚夫,一定會遇到權(quán)限申請恰起、線程切換燃少、數(shù)據(jù)緩存终蒂、異常捕獲恬吕、埋點(diǎn)和方法執(zhí)行時間統(tǒng)計等問題签则。這些都是非常常見的問題,實(shí)現(xiàn)起來也不是很難铐料,不過就是太麻煩了渐裂,還會讓程序多出很多重復(fù)性侨颈、模版化的代碼。
設(shè)計思路
讓我最初接觸到AOP思想的是JakeWharton的hugo,通過閱讀它的源碼之后芯义,讓我對aspectj這項(xiàng)技術(shù)的動態(tài)代碼編織深深地著了迷哈垢。之后我詳細(xì)研究了aspectj相關(guān)的技術(shù),并不斷搜集AOP在Android上的典型應(yīng)用場景扛拨,然后通過aspectj這項(xiàng)技術(shù)去逐一實(shí)現(xiàn)耘分。最后就成就了XAOP這個庫。
解決痛點(diǎn)
- 解決快速點(diǎn)擊的問題
- 解決Android6.0以上動態(tài)權(quán)限申請的問題
- 線程自由切換的問題
- 日志埋點(diǎn)問題
- 緩存問題(磁盤緩存和內(nèi)存緩存)
- 異常捕獲處理
- 業(yè)務(wù)攔截(登陸驗(yàn)證绑警、有效性驗(yàn)證等)
集成指南
添加Gradle依賴
1.先在項(xiàng)目根目錄的 build.gradle
的 repositories 添加:
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
2.再在項(xiàng)目根目錄的 build.gradle
的 dependencies 添加xaop插件:
buildscript {
···
dependencies {
···
classpath 'com.github.xuexiangjys.XAOP:xaop-plugin:1.1.0'
}
}
3.在項(xiàng)目的 build.gradle
中增加依賴并引用xaop插件
apply plugin: 'com.xuexiang.xaop' //引用xaop插件
dependencies {
···
//如果是androidx項(xiàng)目求泰,使用1.1.0版本及以上
implementation 'com.github.xuexiangjys.XAOP:xaop-runtime:1.1.0'
//如果是support項(xiàng)目,請使用1.0.5版本
implementation 'com.github.xuexiangjys.XAOP:xaop-runtime:1.0.5'
}
4.在Application
中進(jìn)行初始化
XAOP.init(this); //初始化插件
XAOP.debug(true); //日志打印切片開啟
XAOP.setPriority(Log.INFO); //設(shè)置日志打印的等級,默認(rèn)為0
//設(shè)置動態(tài)申請權(quán)限切片 申請權(quán)限被拒絕的事件響應(yīng)監(jiān)聽
XAOP.setOnPermissionDeniedListener(new PermissionUtils.OnPermissionDeniedListener() {
@Override
public void onDenied(List<String> permissionsDenied) {
// 權(quán)限申請被拒絕的處理
}
});
//設(shè)置自定義攔截切片的處理攔截器
XAOP.setInterceptor(new Interceptor() {
@Override
public boolean intercept(int type, JoinPoint joinPoint) throws Throwable {
XLogger.d("正在進(jìn)行攔截计盒,攔截類型:" + type);
switch(type) {
case 1:
//做你想要的攔截
break;
case 2:
return true; //return true渴频,直接攔截切片的執(zhí)行
default:
break;
}
return false;
}
});
//設(shè)置自動捕獲異常的處理者
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.在項(xiàng)目根目錄的 build.gradle
的 dependencies 添加 aspectjx 插件:
buildscript {
···
dependencies {
···
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'
}
}
2.在項(xiàng)目的 build.gradle
中增加依賴并引用 aspectjx 插件
apply plugin: 'android-aspectjx' //引用aspectjx插件
aspectjx {
include '項(xiàng)目的applicationId'
}
詳細(xì)使用可參見 kotlin-test 項(xiàng)目進(jìn)行使用.
混淆配置
-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>;
}
基礎(chǔ)使用
快速點(diǎn)擊切片
-
SingleClick
屬性表
屬性名 | 類型 | 默認(rèn)值 | 備注 |
---|---|---|---|
value | long | 1000 | 快速點(diǎn)擊的間隔(ms) |
1.使用@SingleClick
標(biāo)注點(diǎn)擊的方法北启。注意點(diǎn)擊的方法中一定要有點(diǎn)擊控件View作為方法參數(shù)卜朗,否則將不起作用。
2.可以設(shè)置快速點(diǎn)擊的時間間隔咕村,單位:ms场钉。不設(shè)置的話默認(rèn)是1000ms。
@SingleClick(5000)
public void handleOnClick(View v) {
XLogger.e("點(diǎn)擊響應(yīng)懈涛!");
ToastUtil.get().toast("點(diǎn)擊響應(yīng)逛万!");
hello("xuexiangjys", "666666");
}
動態(tài)申請權(quán)限切片
-
Permission
屬性表
屬性名 | 類型 | 默認(rèn)值 | 備注 |
---|---|---|---|
value | String[] | / | 需要申請權(quán)限的集合 |
1.使用@Permission
標(biāo)注需要申請權(quán)限執(zhí)行的方法∨疲可設(shè)置申請一個或多個權(quán)限宇植。
2.使用@Permission
標(biāo)注的方法,在執(zhí)行時會自動判斷是否需要申請權(quán)限埋心。
@SingleClick
@Permission({PermissionConsts.CALENDAR, PermissionConsts.CAMERA, PermissionConsts.LOCATION})
private void handleRequestPermission(View v) {
}
主線程切片
1.使用@MainThread
標(biāo)注需要在主線程中執(zhí)行的方法指郁。
2.使用@MainThread
標(biāo)注的方法,在執(zhí)行時會自動切換至主線程踩窖。
@MainThread
private void doInMainThread(View v) {
mTvHello.setText("工作在主線程");
}
IO線程切片
-
IOThread
屬性表
屬性名 | 類型 | 默認(rèn)值 | 備注 |
---|---|---|---|
value | ThreadType | ThreadType.Fixed | 子線程的類型 |
1.使用@IOThread
標(biāo)注需要在io線程中執(zhí)行的方法坡氯〕亢幔可設(shè)置線程池的類型ThreadType
洋腮,不設(shè)置的話默認(rèn)是Fixed類型。
線程池的類型如下:
- Single:單線程池
- Fixed:多線程池
- Disk:磁盤讀寫線程池(本質(zhì)上是單線程池)
- Network:網(wǎng)絡(luò)請求線程池(本質(zhì)上是多線程池)
2.使用@IOThread
標(biāo)注的方法手形,在執(zhí)行時會自動切換至指定類型的io線程啥供。
@IOThread(ThreadType.Single)
private String doInIOThread(View v) {
return "io線程名:" + Thread.currentThread().getName();
}
日志打印切片
-
DebugLog
屬性表
屬性名 | 類型 | 默認(rèn)值 | 備注 |
---|---|---|---|
priority | int | 0 | 日志的優(yōu)先級 |
1.使用@DebugLog
標(biāo)注需要打印的方法和類】饪罚可設(shè)置打印的優(yōu)先級伙狐,不設(shè)置的話默認(rèn)優(yōu)先級為0涮毫。注意:如果打印的優(yōu)先級比XAOP.setPriority
設(shè)置的優(yōu)先級小的話,將不會進(jìn)行打印贷屎。
2.使用@DebugLog
標(biāo)注的類和方法在執(zhí)行的過程中罢防,方法名、參數(shù)唉侄、執(zhí)行的時間以及結(jié)果都將會被打印咒吐。
3.可調(diào)用XAOP.setISerializer
設(shè)置打印時序列化參數(shù)對象的序列化器。
4.可調(diào)用XAOP.setLogger
設(shè)置打印的實(shí)現(xiàn)接口属划。默認(rèn)提供的是突破4000限制的logcat日志打印恬叹。
@DebugLog(priority = Log.ERROR)
private String hello(String name, String cardId) {
return "hello, " + name + "! Your CardId is " + cardId + ".";
}
內(nèi)存緩存切片
-
MemoryCache
屬性表
屬性名 | 類型 | 默認(rèn)值 | 備注 |
---|---|---|---|
value | String | "" | 內(nèi)存緩存的key |
enableEmpty | boolean | true | 對于String、數(shù)組和集合等同眯,是否允許緩存為空 |
1.使用@MemoryCache
標(biāo)注需要內(nèi)存緩存的方法绽昼。可設(shè)置緩存的key须蜗,不設(shè)置的話默認(rèn)key為方法名(參數(shù)1名=參數(shù)1值|參數(shù)2名=參數(shù)2值|...)
,當(dāng)然你也可以修改key的自動生成規(guī)則硅确,你只需要調(diào)用XAOP.setICacheKeyCreator
即可。
2.標(biāo)注的方法一定要有返回值明肮,否則內(nèi)存緩存切片將不起作用疏魏。
3.使用@MemoryCache
標(biāo)注的方法,可自動實(shí)現(xiàn)緩存策略晤愧。默認(rèn)使用的內(nèi)存緩存是LruCache
大莫。
4.可調(diào)用XAOP.initMemoryCache
設(shè)置內(nèi)存緩存的最大數(shù)量。默認(rèn)是Runtime.getRuntime().maxMemory() / 1024) / 8
@MemoryCache
private String hello(String name, String cardId) {
return "hello, " + name + "! Your CardId is " + cardId + ".";
}
磁盤緩存切片
-
DiskCache
屬性表
屬性名 | 類型 | 默認(rèn)值 | 備注 |
---|---|---|---|
value | String | "" | 內(nèi)存緩存的key |
cacheTime | long | -1 | 緩存時間【單位:s】官份,默認(rèn)是永久有效 |
enableEmpty | boolean | true | 對于String只厘、數(shù)組和集合等,是否允許緩存為空 |
1.使用@DiskCache
標(biāo)注需要磁盤緩存的方法舅巷「嵛叮可設(shè)置緩存的key,不設(shè)置的話默認(rèn)key為方法名(參數(shù)1名=參數(shù)1值|參數(shù)2名=參數(shù)2值|...)
,當(dāng)然你也可以修改key的自動生成規(guī)則钠右,你只需要調(diào)用XAOP.setICacheKeyCreator
即可赋元。
2.可設(shè)置磁盤緩存的有效期,單位:s飒房。不設(shè)置的話默認(rèn)永久有效搁凸。
3.標(biāo)注的方法一定要有返回值,否則磁盤緩存切片將不起作用狠毯。
4.使用@DiskCache
標(biāo)注的方法护糖,可自動實(shí)現(xiàn)緩存策略。默認(rèn)使用的磁盤緩存是JakeWharton的DiskLruCache
嚼松。
5.可調(diào)用XAOP.initDiskCache
設(shè)置磁盤緩存的屬性,包括磁盤序列化器IDiskConverter
嫡良,磁盤緩存的根目錄锰扶,磁盤緩存的最大空間等。
@DiskCache
private String hello(String name, String cardId) {
return "hello, " + name + "! Your CardId is " + cardId + ".";
}
自動捕獲異常切片
-
Safe
屬性表
屬性名 | 類型 | 默認(rèn)值 | 備注 |
---|---|---|---|
value | String | "" | 捕獲異常的標(biāo)志 |
1.使用@Safe
標(biāo)注需要進(jìn)行異常捕獲的方法寝受】琅#可設(shè)置一個異常捕獲的標(biāo)志Flag,默認(rèn)的Flag為當(dāng)前類名.方法名
很澄。
2.調(diào)用XAOP.setIThrowableHandler
設(shè)置捕獲異常的自定義處理者漓帅,可實(shí)現(xiàn)對異常的彌補(bǔ)處理。如果不設(shè)置的話痴怨,將只打印異常的堆棧信息忙干。
3.使用@Safe
標(biāo)注的方法,可自動進(jìn)行異常捕獲,并統(tǒng)一進(jìn)行異常處理浪藻,保證方法平穩(wěn)執(zhí)行捐迫。
@Safe(TRY_CATCH_KEY)
private int getNumber() {
return 100 / 0;
}
自定義攔截切片
-
Intercept
屬性表
屬性名 | 類型 | 默認(rèn)值 | 備注 |
---|---|---|---|
value | int[] | / | 攔截類型 |
1.使用@Intercept
標(biāo)注需要進(jìn)行攔截的方法和類“可設(shè)置申請一個或多個攔截類型施戴。
2.如果不調(diào)用XAOP.setInterceptor
設(shè)置切片攔截的攔截器的話,自定義攔截切片將不起作用萌丈。
3.使用@Intercept
標(biāo)注的類和方法赞哗,在執(zhí)行時將自動調(diào)用XAOP
設(shè)置的攔截器進(jìn)行攔截處理。如果攔截器處理返回true的話辆雾,該類或方法的執(zhí)行將被攔截肪笋,不執(zhí)行。
4.使用@Intercept
可以靈活地進(jìn)行切片攔截度迂。比如用戶登錄權(quán)限等藤乙。
@SingleClick(5000)
@DebugLog(priority = Log.ERROR)
@Intercept(3)
public void handleOnClick(View v) {
XLogger.e("點(diǎn)擊響應(yīng)!");
ToastUtil.get().toast("點(diǎn)擊響應(yīng)惭墓!");
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 + ".";
}
【注意】:當(dāng)有多個切片注解修飾時坛梁,一般是從上至下依次順序執(zhí)行。
進(jìn)階使用
登陸驗(yàn)證
在應(yīng)用中腊凶,對于部分功能划咐,如:個人中心、錢包钧萍、收藏等需要我們驗(yàn)證登錄的功能褐缠,我們都可以通過
@Intercept
業(yè)務(wù)攔截切片來實(shí)現(xiàn)。
- 定義業(yè)務(wù)攔截類型
// 登錄校驗(yàn)攔截類型
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) { //沒登錄,進(jìn)行攔截
ToastUtils.toast("請先進(jìn)行登陸划煮!");
ActivityUtils.startActivity(LoginActivity.class);
return true; //return true送丰,直接攔截切片的執(zhí)行
}
break;
default:
break;
}
return false;
}
});
- 在需要攔截的地方增加
@Intercept
標(biāo)注
@Intercept(INTERCEPT_LOGIN)
public void doSomeThing() {
ToastUtils.toast("已登陸過啦~~");
}
常見問題
接入的問題
使用前缔俄,請一定要仔細(xì)閱讀集成指南弛秋,只要你每一步都參照文檔上寫的來接入器躏,是不會有任何問題的!
1.問:我的項(xiàng)目是kotlin項(xiàng)目蟹略,我該怎么使用登失?
答:kotlin項(xiàng)目的配置,只需要在原先項(xiàng)目的基礎(chǔ)上加上aspectjx 插件即可挖炬,詳情請參考兼容Kotlin語法配置 揽浙。
2.問:為什么我每次運(yùn)行編譯時,一直報錯Invalid byte tag in constant pool
意敛,而且會自動生成一個ajcore.xxxxxxxxx.txt
文件?
答:這里很有可能你的項(xiàng)目目前還是使用的androidx版本馅巷,但是你使用的XAOP版本是support版本,導(dǎo)致編譯失敗草姻。這里需要強(qiáng)調(diào)的是钓猬,如果你的項(xiàng)目是support版本,請使用1.0.5版本撩独;如果你的項(xiàng)目是androidx版本敞曹,請使用1.1.0及以上版本。
3.問:為什么我編譯都通過了综膀,但是使用任何一個切片都沒有起任何作用?
答:這里可能的原因有兩個澳迫。
- 1.你使用的XAOP版本和你的項(xiàng)目版本不匹配導(dǎo)致。比如你的項(xiàng)目是androidx版本剧劝,但是你卻使用XAOP的support版本橄登,這樣瞎配的話,切片是不會起任何作用的讥此。
- 2.你忘記在項(xiàng)目的
build.gradle
中增加xaop插件的引用了示绊。
apply plugin: 'com.xuexiang.xaop' //引用xaop插件
使用的問題
1.問:為什么我使用@SingleClick
標(biāo)注點(diǎn)擊的方法不起作用?
答:被@SingleClick
標(biāo)注的方法中暂论,一定要有點(diǎn)擊控件View作為方法參數(shù)面褐,否則將不起作用。
2.問:為什么我使用@Permission
標(biāo)注的方法取胎,返回值失效了展哭?
答:由于動態(tài)申請權(quán)限是一個異步的操作,所以被@Permission
標(biāo)注的方法是不能有返回值的闻蛀。