AndroidAOP 是專屬于 Android 端 Aop 框架歉嗓,只需一個注解就可以請求權(quán)限、切換線程背蟆、禁止多點鉴分、監(jiān)測生命周期等等哮幢,本庫不是基于 AspectJ 實現(xiàn)的 Aop,當然你也可以定制出屬于你的 Aop 代碼
AndroidAOP - Github鏈接
特色功能
1冠场、本庫內(nèi)置了開發(fā)中常用的一些切面注解供你使用
2家浇、本庫支持讓你自己做切面,語法簡單易上手
3碴裙、本庫同步支持 Java 和 Kotlin 代碼
4钢悲、本庫支持切入三方庫
5、本庫支持切點方法為 Lambda 表達式的情況
6舔株、本庫支持生成所有切點信息Json文件莺琳,方便一覽所有切點位置在此配置
7、本庫不是基于 AspectJ 實現(xiàn)的载慈,織入代碼量極少惭等,侵入性極低
點此下載apk
版本限制
最低Gradle版本:8.0
最低SDK版本:minSdkVersion >= 21
使用步驟
在開始之前可以給項目一個Star嗎?非常感謝办铡,你的支持是我唯一的動力辞做。歡迎Star和Issues!
一、在項目根目錄下的build.gradle添加(必須)
buildscript {
dependencies {
//必須項 ??
classpath 'io.github.FlyJingFish.AndroidAop:android-aop-plugin:1.2.0'
}
}
plugins {
//非必須項 ??寡具,如果需要自定義切面秤茅,并且使用 android-aop-ksp 這個庫的話需要配置 ,下邊版本號根據(jù)你項目的 Kotlin 版本決定
id 'com.google.devtools.ksp' version '1.8.0-1.0.9' apply false
}
二童叠、在 app 的build.gradle添加(此步為必須項)
??注意:??此步為必須項??
//必須項 ??
plugins {
...
id 'android.aop'//最好放在最后一行
}
三框喳、引入依賴庫
plugins {
//非必須項 ??,如果需要自定義切面厦坛,并且使用 android-aop-ksp 這個庫的話需要配置
id 'com.google.devtools.ksp'
}
dependencies {
//必須項 ??
implementation 'io.github.FlyJingFish.AndroidAop:android-aop-core:1.2.0'
implementation 'io.github.FlyJingFish.AndroidAop:android-aop-annotation:1.2.0'
//非必須項 ??五垮,如果你想自定義切面需要用到,??支持Java和Kotlin代碼寫的切面
ksp 'io.github.FlyJingFish.AndroidAop:android-aop-ksp:1.2.0'
//非必須項 ??杜秸,如果你想自定義切面需要用到放仗,??只適用于Java代碼寫的切面
annotationProcessor 'io.github.FlyJingFish.AndroidAop:android-aop-processor:1.2.0'
//??上邊的 android-aop-ksp 和 android-aop-processor 二選一
}
提示:ksp 或 annotationProcessor只是在當前 module 起作用,在哪個 module 中有自定義切面代碼就加在哪個 module撬碟,必須依賴項可以通過 api 方式只加到公共 module 上
四诞挨、在 app 的build.gradle添加 androidAopConfig 配置項(此步為可選配置項)
plugins {
...
}
androidAopConfig {
// enabled 為 false 切面不再起作用,默認不寫為 true
enabled true
// include 不設(shè)置默認全部掃描小作,設(shè)置后只掃描設(shè)置的包名的代碼
include '你項目的包名','自定義module的包名','自定義module的包名'
// exclude 是掃描時排除的包
// 可排除 kotlin 相關(guān)亭姥,提高速度
exclude 'kotlin.jvm', 'kotlin.internal','kotlinx.coroutines.internal', 'kotlinx.coroutines.android'
// verifyLeafExtends 是否開啟驗證葉子繼承稼钩,默認打開顾稀,如果沒有設(shè)置 @AndroidAopMatchClassMethod 的 type = MatchType.LEAF_EXTENDS,可以關(guān)閉
verifyLeafExtends true
//默認關(guān)閉坝撑,開啟在 Build 或 打包后 將會生成切點信息json文件在 app/build/tmp/cutInfo.json
cutInfoJson false
}
android {
...
}
提示:合理使用 include 和 exclude 可提高編譯速度静秆,建議直接使用 include 設(shè)置你項目的相關(guān)包名(包括 app 和自定義 module 的)
另外設(shè)置此處之后由于 Android Studio 可能有緩存粮揉,建議重啟 AS 并 clean 下項目再繼續(xù)開發(fā)
本庫內(nèi)置了一些功能注解可供你直接使用
注解名稱 | 參數(shù)說明 | 功能說明 |
---|---|---|
@SingleClick | value = 快速點擊的間隔,默認1000ms | 單擊注解抚笔,加入此注解扶认,可使你的方法只有單擊時才可進入 |
@DoubleClick | value = 兩次點擊的最大用時,默認300ms | 雙擊注解殊橙,加入此注解辐宾,可使你的方法雙擊時才可進入 |
@IOThread | ThreadType = 線程類型 | 切換到子線程的操作,加入此注解可使你的方法內(nèi)的代碼切換到子線程執(zhí)行 |
@MainThread | 無參數(shù) | 切換到主線程的操作膨蛮,加入此注解可使你的方法內(nèi)的代碼切換到主線程執(zhí)行 |
@OnLifecycle | value = Lifecycle.Event | 監(jiān)聽生命周期的操作叠纹,加入此注解可使你的方法內(nèi)的代碼在對應(yīng)生命周期內(nèi)才去執(zhí)行 |
@TryCatch | value = 你自定義加的一個flag | 加入此注解可為您的方法包裹一層 try catch 代碼 |
@Permission | value = 權(quán)限的字符串數(shù)組 | 申請權(quán)限的操作,加入此注解可使您的代碼在獲取權(quán)限后才執(zhí)行 |
@Scheduled | initialDelay = 延遲開始時間 interval = 間隔 repeatCount = 重復次數(shù) isOnMainThread = 是否主線程 id = 唯一標識 |
定時任務(wù)敞葛,加入此注解誉察,可使你的方法每隔一段時間執(zhí)行一次,調(diào)用AndroidAop.shutdownNow(id)或AndroidAop.shutdown(id)可停止 |
@Delay | delay = 延遲時間 isOnMainThread = 是否主線程 id = 唯一標識 |
延遲任務(wù)惹谐,加入此注解持偏,可使你的方法延遲一段時間執(zhí)行,調(diào)用AndroidAop.shutdownNow(id)或AndroidAop.shutdown(id)可取消 |
@CustomIntercept | value = 你自定義加的一個字符串數(shù)組的flag | 自定義攔截氨肌,配合 AndroidAop.setOnCustomInterceptListener 使用鸿秆,屬于萬金油 |
這塊強調(diào)一下 @OnLifecycle
- 1、@OnLifecycle 加到的方法所屬對象必須是屬于直接或間接繼承自 FragmentActivity 或 Fragment的方法才有用儒飒,或者注解方法的對象實現(xiàn) LifecycleOwner 也可以
- 2谬莹、如果第1點不符合的情況下,可以給切面方法第一個參數(shù)設(shè)置為第1點的類型桩了,在調(diào)用切面方法傳入也是可以的附帽,例如:
public class StaticClass {
@SingleClick(5000)
@OnLifecycle(Lifecycle.Event.ON_RESUME)
public static void onStaticPermission(MainActivity activity, int maxSelect , ThirdActivity.OnPhotoSelectListener back){
back.onBack();
}
}
下面再著重介紹下 @TryCatch @Permission @CustomIntercept
- @TryCatch 使用此注解你可以設(shè)置以下設(shè)置(非必須)
AndroidAop.INSTANCE.setOnThrowableListener(new OnThrowableListener() {
@Nullable
@Override
public Object handleThrowable(@NonNull String flag, @Nullable Throwable throwable,TryCatch tryCatch) {
// TODO: 2023/11/11 發(fā)生異常可根據(jù)你當時傳入的flag作出相應(yīng)處理井誉,如果需要改寫返回值邻储,則在 return 處返回即可
return 3;
}
});
- @Permission 使用此注解必須配合以下設(shè)置(??此步為必須設(shè)置的,否則是沒效果的)
AndroidAop.INSTANCE.setOnPermissionsInterceptListener(new OnPermissionsInterceptListener() {
@SuppressLint("CheckResult")
@Override
public void requestPermission(@NonNull ProceedJoinPoint joinPoint, @NonNull Permission permission, @NonNull OnRequestPermissionListener call) {
Object target = joinPoint.getTarget();
if (target instanceof FragmentActivity){
RxPermissions rxPermissions = new RxPermissions((FragmentActivity) target);
rxPermissions.request(permission.value()).subscribe(call::onCall);
}else if (target instanceof Fragment){
RxPermissions rxPermissions = new RxPermissions((Fragment) target);
rxPermissions.request(permission.value()).subscribe(call::onCall);
}else{
// TODO: target 不是 FragmentActivity 或 Fragment 杠愧,說明注解所在方法不在其中篮撑,請自行處理這種情況
// 建議:切點方法第一個參數(shù)可以設(shè)置為 FragmentActivity 或 Fragment ,然后 joinPoint.args[0] 就可以拿到
}
}
});
- @CustomIntercept 使用此注解你必須配合以下設(shè)置(??此步為必須設(shè)置的在岂,否則還有什么意義呢奔则?)
AndroidAop.INSTANCE.setOnCustomInterceptListener(new OnCustomInterceptListener() {
@Nullable
@Override
public Object invoke(@NonNull ProceedJoinPoint joinPoint, @NonNull CustomIntercept customIntercept) {
// TODO: 2023/11/11 在此寫你的邏輯 在合適的地方調(diào)用 joinPoint.proceed(),
// joinPoint.proceed(args)可以修改方法傳入的參數(shù)蔽午,如果需要改寫返回值易茬,則在 return 處返回即可
return null;
}
});
??上邊三個監(jiān)聽,最好放到你的 application 中
此外本庫也同樣支持讓你自己做切面,實現(xiàn)起來非常簡單抽莱!
本庫通過 @AndroidAopPointCut 和 @AndroidAopMatchClassMethod 兩種注解范抓,實現(xiàn)自定義切面
一、@AndroidAopPointCut 是在方法上通過注解的形式做切面的食铐,上述中注解都是通過這個做的匕垫,詳細使用請看wiki文檔
??注意:自定義的注解(也就是被 @AndroidAopPointCut 注解的注解類)如果是 Kotlin 代碼請用 android-aop-ksp 那個庫
下面以 @CustomIntercept 為例介紹下該如何使用
- 創(chuàng)建注解
@AndroidAopPointCut(CustomInterceptCut.class)
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomIntercept {
String[] value() default {};
}
- 創(chuàng)建注解處理切面的類(需要實現(xiàn) BasePointCut 接口,它的泛型填上邊的注解)
class CustomInterceptCut : BasePointCut<CustomIntercept> {
override fun invoke(
joinPoint: ProceedJoinPoint,
annotation: CustomIntercept //annotation就是你加到方法上的注解
): Any? {
// 在此寫你的邏輯
// joinPoint.proceed() 表示繼續(xù)執(zhí)行切點方法的邏輯虐呻,不調(diào)用此方法不會執(zhí)行切點方法里邊的代碼
// 關(guān)于 ProceedJoinPoint 可以看wiki 文檔象泵,詳細點擊下方鏈接
return joinPoint.proceed()
}
}
關(guān)于 ProceedJoinPoint 使用說明,下文的 ProceedJoinPoint 同理
- 使用
直接將你寫的注解加到任意一個方法上斟叼,例如加到了 onCustomIntercept() 當 onCustomIntercept() 被調(diào)用時首先會進入到上文提到的 CustomInterceptCut 的 invoke 方法上
@CustomIntercept("我是自定義數(shù)據(jù)")
fun onCustomIntercept(){
}
二单芜、@AndroidAopMatchClassMethod 是做匹配某類及其對應(yīng)方法的切面的
匹配方法支持精準匹配,點此看wiki詳細使用文檔
??注意:自定義的匹配類方法切面(也就是被 @AndroidAopMatchClassMethod 注解的代碼)如果是 Kotlin 代碼請用 android-aop-ksp 那個庫
- 例子一
package com.flyjingfish.test_lib;
public class TestMatch {
public void test1(int value1,String value2){
}
public String test2(int value1,String value2){
return value1+value2;
}
}
假如 TestMatch 是要匹配的類犁柜,而你想要匹配到 test2 這個方法洲鸠,下邊是匹配寫法:
package com.flyjingfish.test_lib.mycut;
@AndroidAopMatchClassMethod(
targetClassName = "com.flyjingfish.test_lib.TestMatch",
methodName = ["test2"],
type = MatchType.SELF
)
class MatchTestMatchMethod : MatchClassMethod {
override fun invoke(joinPoint: ProceedJoinPoint, methodName: String): Any? {
Log.e("MatchTestMatchMethod","======"+methodName+",getParameterTypes="+joinPoint.getTargetMethod().getParameterTypes().length);
// 在此寫你的邏輯
//不想執(zhí)行原來方法邏輯,??就不調(diào)用下邊這句
return joinPoint.proceed()
}
}
可以看到上方 AndroidAopMatchClassMethod 設(shè)置的 type 是 MatchType.SELF 表示只匹配 TestMatch 這個類自身馋缅,不考慮其子類
- 例子二
假如想 Hook 所有的 android.view.View.OnClickListener 的 onClick扒腕,說白了就是想全局監(jiān)測所有的設(shè)置 OnClickListener 的點擊事件,代碼如下:
@AndroidAopMatchClassMethod(
targetClassName = "android.view.View.OnClickListener",
methodName = ["onClick"],
type = MatchType.EXTENDS //type 一定是 EXTENDS 因為你想 hook 所有繼承了 OnClickListener 的類
)
class MatchOnClick : MatchClassMethod {
// @SingleClick(5000) //聯(lián)合 @SingleClick 萤悴,給所有點擊增加防多點瘾腰,6不6
override fun invoke(joinPoint: ProceedJoinPoint, methodName: String): Any? {
Log.e("MatchOnClick", "=====invoke=====$methodName")
return joinPoint.proceed()
}
}
可以看到上方 AndroidAopMatchClassMethod 設(shè)置的 type 是 MatchType.EXTENDS 表示匹配所有繼承自 OnClickListener 的子類,另外更多繼承方式覆履,請參考Wiki文檔
??注意:如果子類沒有該方法蹋盆,則切面無效,另外對同一個類的同一個方法不要做多次匹配硝全,否則只有一個會生效
匹配切面實用場景:
例如你想做退出登陸邏輯時可以使用上邊這個栖雾,只要在頁面內(nèi)跳轉(zhuǎn)就可以檢測是否需要退出登陸
又或者你想在三方庫某個方法上設(shè)置切面,可以直接設(shè)置對應(yīng)類名伟众,對應(yīng)方法析藕,然后 type = MatchType.SELF,這樣可以侵入三方庫的代碼凳厢,當然這么做記得修改上文提到的 androidAopConfig 的配置
詳細使用請看wiki文檔
常見問題
1账胧、Build時報錯 "ZipFile invalid LOC header (bad signature)"
- 請重啟Android Studio,然后 clean 項目
2先紫、 同一個方法存在多個注解或匹配切面時治泥,怎么處理的
- 多個切面疊加到一個方法上時注解優(yōu)先于匹配切面(上文的匹配切面),注解切面之間從上到下依次執(zhí)行
- 調(diào)用 proceed 才會執(zhí)行下一個切面遮精,多個切面中最后一個切面執(zhí)行 proceed 才會調(diào)用切入方法內(nèi)的代碼
- 在前邊切面中調(diào)用 proceed(args) 可更新方法傳入?yún)?shù)居夹,并在下一個切面中也會拿到上一層更新的參數(shù)
- 存在異步調(diào)用proceed時,第一個異步調(diào)用 proceed 切面的返回值(就是 invoke 的返回值)就是切入方法的返回值;
混淆規(guī)則
下邊是涉及到本庫的一些必須混淆規(guī)則
# AndroidAop必備混淆規(guī)則 -----start-----
-keep class * {
@androidx.annotation.Keep <fields>;
}
-keepnames class * implements com.flyjingfish.android_aop_annotation.base.BasePointCut
-keepnames class * implements com.flyjingfish.android_aop_annotation.base.MatchClassMethod
-keep class * implements com.flyjingfish.android_aop_annotation.base.BasePointCut{
public <init>();
}
-keep class * implements com.flyjingfish.android_aop_annotation.base.MatchClassMethod{
public <init>();
}
# AndroidAop必備混淆規(guī)則 -----end-----