
1 、本库内置了开发中常用的一些切面注解供你使用
2 、本库支持让你自己做切面,语法简单易上手
3 、本库同步支持 Java 和 Kotlin 代码
4 、本库支持切入三方库
5 、本库支持切点方法为 Lambda 表达式的情况
6 、本库支持生成所有切点信息 Json 文件,方便一览所有切点位置在此配置
7 、本库不是基于 AspectJ 实现的,织入代码量极少,侵入性极低
最低 Gradle 版本:8.0
最低 SDK 版本:minSdkVersion >= 21
在开始之前可以给项目一个 Star 吗?非常感谢,你的支持是我唯一的动力。欢迎 Star 和 Issues!
buildscript { dependencies { //必须项 classpath 'io.github.FlyJingFish.AndroidAop:android-aop-plugin:1.2.3' } } plugins { //非必须项 ,如果需要自定义切面,并且使用 android-aop-ksp 这个库的话需要配置 ,下边版本号根据你项目的 Kotlin 版本决定 id 'com.google.devtools.ksp' version '1.8.0-1.0.9' apply false } //必须项 plugins { ... id 'android.aop'//最好放在最后一行 } plugins { //非必须项 ,如果需要自定义切面,并且使用 android-aop-ksp 这个库的话需要配置 id 'com.google.devtools.ksp' } dependencies { //必须项 implementation 'io.github.FlyJingFish.AndroidAop:android-aop-core:1.2.3' implementation 'io.github.FlyJingFish.AndroidAop:android-aop-annotation:1.2.3' //非必须项 ,如果你想自定义切面需要用到,支持 Java 和 Kotlin 代码写的切面 ksp 'io.github.FlyJingFish.AndroidAop:android-aop-ksp:1.2.3' //非必须项 ,如果你想自定义切面需要用到,只适用于 Java 代码写的切面 annotationProcessor 'io.github.FlyJingFish.AndroidAop:android-aop-processor:1.2.3' //上边的 android-aop-ksp 和 android-aop-processor 二选一 } 提示:ksp 或 annotationProcessor 只是在当前 module 起作用,在哪个 module 中有自定义切面代码就加在哪个 module ,必须依赖项可以通过 api 方式只加到公共 module 上
plugins { ... } androidAopConfig { // enabled 为 false 切面不再起作用,默认不写为 true enabled true // include 不设置默认全部扫描,设置后只扫描设置的包名的代码 include '你项目的包名','自定义 module 的包名','自定义 module 的包名' // exclude 是扫描时排除的包 // 可排除 kotlin 相关,提高速度 exclude 'kotlin.jvm', 'kotlin.internal','kotlinx.coroutines.internal', 'kotlinx.coroutines.android' // verifyLeafExtends 是否开启验证叶子继承,默认打开,如果没有设置 @AndroidAopMatchClassMethod 的 type = MatchType.LEAF_EXTENDS ,可以关闭 verifyLeafExtends true //默认关闭,开启在 Build 或 打包后 将会生成切点信息 json 文件在 app/build/tmp/cutInfo.json cutInfoJson false } android { ... } 提示:合理使用 include 和 exclude 可提高编译速度,建议直接使用 include 设置你项目的相关包名(包括 app 和自定义 module 的)
另外设置此处之后由于 Android Studio 可能有缓存,建议重启 AS 并 clean 下项目再继续开发
| 注解名称 | 参数说明 | 功能说明 |
|---|---|---|
| @SingleClick | value = 快速点击的间隔,默认 1000ms | 单击注解,加入此注解,可使你的方法只有单击时才可进入 |
| @DoubleClick | value = 两次点击的最大用时,默认 300ms | 双击注解,加入此注解,可使你的方法双击时才可进入 |
| @IOThread | ThreadType = 线程类型 | 切换到子线程的操作,加入此注解可使你的方法内的代码切换到子线程执行 |
| @MainThread | 无参数 | 切换到主线程的操作,加入此注解可使你的方法内的代码切换到主线程执行 |
| @OnLifecycle | value = Lifecycle.Event | 监听生命周期的操作,加入此注解可使你的方法内的代码在对应生命周期内才去执行 |
| @TryCatch | value = 你自定义加的一个 flag | 加入此注解可为您的方法包裹一层 try catch 代码 |
| @Permission | value = 权限的字符串数组 | 申请权限的操作,加入此注解可使您的代码在获取权限后才执行 |
| @Scheduled | initialDelay = 延迟开始时间 interval = 间隔 repeatCount = 重复次数 isOnMainThread= 是否主线程 id = 唯一标识 | 定时任务,加入此注解,可使你的方法每隔一段时间执行一次,调用 AndroidAop.shutdownNow(id)或 AndroidAop.shutdown(id)可停止 |
| @Delay | delay = 延迟时间 isOnMainThread= 是否主线程 id = 唯一标识 | 延迟任务,加入此注解,可使你的方法延迟一段时间执行,调用 AndroidAop.shutdownNow(id)或 AndroidAop.shutdown(id)可取消 |
| @CustomIntercept | value = 你自定义加的一个字符串数组的 flag | 自定义拦截,配合 AndroidAop.setOnCustomInterceptListener 使用,属于万金油 |
public class StaticClass { @SingleClick(5000) @OnLifecycle(Lifecycle.Event.ON_RESUME) public static void onStaticPermission(MainActivity activity, int maxSelect , ThirdActivity.OnPhotoSelectListener back){ back.onBack(); } } AndroidAop.INSTANCE.setOnThrowableListener(new OnThrowableListener() { @Nullable @Override public Object handleThrowable(@NonNull String flag, @Nullable Throwable throwable,TryCatch tryCatch) { // TODO: 2023/11/11 发生异常可根据你当时传入的 flag 作出相应处理,如果需要改写返回值,则在 return 处返回即可 return 3; } }); 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 ,说明注解所在方法不在其中,请自行处理这种情况 // 建议:切点方法第一个参数可以设置为 FragmentActivity 或 Fragment ,然后 joinPoint.args[0] 就可以拿到 } } }); AndroidAop.INSTANCE.setOnCustomInterceptListener(new OnCustomInterceptListener() { @Nullable @Override public Object invoke(@NonNull ProceedJoinPoint joinPoint, @NonNull CustomIntercept customIntercept) { // TODO: 2023/11/11 在此写你的逻辑 在合适的地方调用 joinPoint.proceed(), // joinPoint.proceed(args)可以修改方法传入的参数,如果需要改写返回值,则在 return 处返回即可 return null; } }); 上边三个监听,最好放到你的 application 中
注意:自定义的注解(也就是被 @AndroidAopPointCut 注解的注解类)如果是 Kotlin 代码请用 android-aop-ksp 那个库
下面以 @CustomIntercept 为例介绍下该如何使用
@AndroidAopPointCut(CustomInterceptCut.class) @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface CustomIntercept { String[] value() default {}; } class CustomInterceptCut : BasePointCut<CustomIntercept> { override fun invoke( joinPoint: ProceedJoinPoint, annotation: CustomIntercept //annotation 就是你加到方法上的注解 ): Any? { // 在此写你的逻辑 // joinPoint.proceed() 表示继续执行切点方法的逻辑,不调用此方法不会执行切点方法里边的代码 // 关于 ProceedJoinPoint 可以看 wiki 文档,详细点击下方链接 return joinPoint.proceed() } } 关于 ProceedJoinPoint 使用说明,下文的 ProceedJoinPoint 同理
直接将你写的注解加到任意一个方法上,例如加到了 onCustomIntercept() 当 onCustomIntercept() 被调用时首先会进入到上文提到的 CustomInterceptCut 的 invoke 方法上
@CustomIntercept("我是自定义数据") fun onCustomIntercept(){ } 匹配方法支持精准匹配,点此看 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); // 在此写你的逻辑 //不想执行原来方法逻辑,就不调用下边这句 return joinPoint.proceed() } } 可以看到上方 AndroidAopMatchClassMethod 设置的 type 是 MatchType.SELF 表示只匹配 TestMatch 这个类自身,不考虑其子类
假如想 Hook 所有的 android.view.View.OnClickListener 的 onClick ,说白了就是想全局监测所有的设置 OnClickListener 的点击事件,代码如下:
@AndroidAopMatchClassMethod( targetClassName = "android.view.View.OnClickListener", methodName = ["onClick"], type = MatchType.EXTENDS //type 一定是 EXTENDS 因为你想 hook 所有继承了 OnClickListener 的类 ) class MatchOnClick : MatchClassMethod { // @SingleClick(5000) //联合 @SingleClick ,给所有点击增加防多点,6 不 6 override fun invoke(joinPoint: ProceedJoinPoint, methodName: String): Any? { Log.e("MatchOnClick", "=====invoke=====$methodName") return joinPoint.proceed() } } 可以看到上方 AndroidAopMatchClassMethod 设置的 type 是 MatchType.EXTENDS 表示匹配所有继承自 OnClickListener 的子类,另外更多继承方式,请参考 Wiki 文档
注意:如果子类没有该方法,则切面无效,另外对同一个类的同一个方法不要做多次匹配,否则只有一个会生效
例如你想做退出登陆逻辑时可以使用上边这个,只要在页面内跳转就可以检测是否需要退出登陆
又或者你想在三方库某个方法上设置切面,可以直接设置对应类名,对应方法,然后 type = MatchType.SELF ,这样可以侵入三方库的代码,当然这么做记得修改上文提到的 androidAopConfig 的配置
1 、Build 时报错 "ZipFile invalid LOC header (bad signature)"
2 、 同一个方法存在多个注解或匹配切面时,怎么处理的
下边是涉及到本库的一些必须混淆规则
# AndroidAop 必备混淆规则 -----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 必备混淆规则 -----end-----