AndroidAOP 是专属于 Android 端 Aop 框架,只需一个注解就可以请求权限、切换线程、禁止多点、监测生命周期等等,本库不是基于 AspectJ 实现的 Aop,当然你也可以定制出属于你的 Aop 代码
告别样板代码,用 AOP 解放双手,一行注解,织就高效 Android 应用
----AndroidAOP,专为开发者而生!
特色功能
1、本库内置了开发中常用的一些切面注解供你使用
2、本库支持让你自己做切面,语法简单易上手
3、本库同步支持 Java项目 和 Kotlin项目
4、本库支持切入三方库
5、本库支持切点方法为 Lambda 表达式的情况
6、本库支持切点方法为 suspend 修饰的协程函数
7、本库支持生成所有切点信息json、html文件,方便一览所有切点位置
8、本库支持多种快速开发模式,让你打包速度几乎不变
9、本库支持 组件化开发
10、本库是纯静态织入AOP代码
11、本库不是基于 AspectJ 实现的,织入代码量极少,侵入性极低
12、丰富完善的使用文档助你完全理解本库的使用规则
13、更有帮助你生成切面代码的插件助手供你使用
点此下载apk
版本限制
最低Gradle版本:7.6
最低SDK版本:minSdkVersion >= 21
使用步骤
在开始之前可以给项目一个Star吗?非常感谢,你的支持是我唯一的动力。欢迎Star和Issues!
一、引入插件,下边两种方式二选一(必须)
方式一:apply 方式(推荐)
1、在 项目根目录 的 build.gradle 里依赖插件
新版本
plugins {
//必须项 👇 apply 设置为 true 自动为所有module“预”配置debugMode,false则需手动配置
id "io.github.FlyJingFish.AndroidAop.android-aop" version "2.6.4" apply true
}
或者老版本
buildscript {
dependencies {
//必须项 👇
classpath 'io.github.FlyJingFish.AndroidAop:android-aop-plugin:2.6.4'
}
}
// 👇加上这句自动为所有module“预”配置debugMode,不加则按下边步骤五的方式二
apply plugin: "android.aop"
2、在 app 的 build.gradle 添加
新版本
//必须项 👇
plugins {
...
id 'android.aop'//最好放在最后一行
}
或者老版本
//必须项 👇
apply plugin: 'android.aop' //最好放在最后一行
方式二:plugins 方式(不推荐)
plugins 方式(不推荐)直接在 app 的 build.gradle 添加
//必须项 👇
plugins {
...
id "io.github.FlyJingFish.AndroidAop.android-aop" version "2.6.4"
}
二、如果你需要自定义切面,并且代码是 Kotlin (非必须)
1、在 项目根目录 的 build.gradle 里依赖插件
plugins {
//非必须项 👇,如果需要自定义切面,并且使用 android-aop-ksp 这个库的话需要配置 ,下边版本号根据你项目的 Kotlin 版本决定
id 'com.google.devtools.ksp' version '1.8.0-1.0.9' apply false
}
三、引入依赖库(必须)
plugins {
//非必须项 👇,如果需要自定义切面,并且使用 android-aop-ksp 这个库的话需要配置
id 'com.google.devtools.ksp'
}
dependencies {
//必须项 👇
implementation 'io.github.FlyJingFish.AndroidAop:android-aop-core:2.6.4'
//非必须项 👇这个包提供了一些常见的注解切面
implementation 'io.github.FlyJingFish.AndroidAop:android-aop-extra:2.6.4'
//必须项 👇如果您项目内已经有了这项不用加也可以
implementation 'androidx.appcompat:appcompat:1.3.0' // 至少在1.3.0及以上
//非必须项 👇,如果你想自定义切面需要用到,⚠️支持Java和Kotlin代码写的切面
ksp 'io.github.FlyJingFish.AndroidAop:android-aop-ksp:2.6.4'
//非必须项 👇,如果你想自定义切面需要用到,⚠️只适用于Java代码写的切面
annotationProcessor 'io.github.FlyJingFish.AndroidAop:android-aop-processor:2.6.4'
//⚠️上边的 android-aop-ksp 和 android-aop-processor 二选一
//如果只是使用 android-aop-extra 中的功能就不需要选择这两项
}
提示:ksp 或 annotationProcessor只能扫描当前 module ,在哪个 module 中有自定义切面代码就加在哪个 module,但是自定义的切面代码是全局生效的;必须依赖项可以通过 api 方式只加到公共 module 上
四、在 app 的build.gradle添加 androidAopConfig 配置项(此步为可选配置项)
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
//默认开启,设置 false 后会没有增量编译效果 筛选(关键字: AndroidAOP woven info code) build 输出日志可看时间
increment true//修改、增加、删除匹配切面的话,就会走全量编译
}
android {
...
}
五、开发中可设置打包方式(此步为可选配置项,建议配置此项加速开发)
📌 方式一(fastDex 模式)
在 根目录 的 gradle.properties 添加如下设置
androidAop.fastDex = true //加速 dexBuilder阶段(默认false)
androidAop.fastDex.variantOnlyDebug = false //只在 debug 起作用(默认false)
1、
androidAop.fastDex设置为 true 时则会对 dexBuilder 任务进行 增量编译 优化加速,请注意此项设置在不处于 debugMode 模式下才有作用
2、androidAop.fastDex.variantOnlyDebug设置为 true 时 release 包会忽略androidAop.fastDex = true的设置
3、如果你项目中存在其他使用toTransform的插件,请调整任务执行顺序将xxAssembleAndroidAopTask任务放在最后,否则效果将会有所折扣
4、如果你项目有其他使用toTransform的插件,如某些 Router,建议使用本方式一
📌 方式二(debugMode 模式)
1⃣️ 为 所有的子module 也依赖插件,请按照上述 步骤一的方式一配置项目,然后以下方式二选一
-
自动模式:(推荐)
按照上述步骤一的方式一配置项目,就可以了。这个方式自动为所有 Android 的 module 应用 debugMode
-
手动模式:(不推荐)请按照上述步骤一的方式一配置项目后,手动为 需要的子 module 模块 设置,例如:
plugins { ... id 'android.aop'//最好放在最后一行,尤其是必须在 `id 'com.android.application'` 或 `id 'com.android.library'` 的后边 }
1、这个方式可以只为你加过的 module 应用 debugMode,没加的 module 里边的相关切面不会生效
2、如果你的 module 是 Java或Kotlin 的 Library,方式一只能让所有的 Android 的 Library,需要采用方式二单独为你的 module 配置才会生效
2⃣️ 在 根目录 的 gradle.properties 添加如下设置
androidAop.debugMode=true //设置为 true 走您项目当前的打包方式 ,false 则为全量打包方式,不写默认false
1、请注意设置为 true 时编译速度会变快但部分功能将失效,只会为设置的 module 织入 aop 代码,三方jar包 不会织入代码,因此打正式包时请注意关闭此项配置并clean项目
2、如果设置了org.gradle.parallel = true,如有bug请注意调整各个 module compileXXJavaWithJavac 任务的顺序,不会的可以选择直接关闭这项配置
3⃣️ 在 根目录 的 gradle.properties 添加如下设置
androidAop.debugMode.variantOnlyDebug = true //默认不写这项就是true
1、这项不写默认就是true,请注意设置为 true 时 release 包会忽略
androidAop.debugMode = true的设置自动走全量打包方式(相当于临时关闭了debugMode),设为 false 时则没有这种效果
2、此项功能默认开启,因此release包无需手动关闭androidAop.debugMode
3、此项只对 Android 的 Library 有效,对 Java 或 Kotlin 的 Library 无效
4⃣️在 根目录 的 gradle.properties 添加如下设置(非必须项)
androidAop.debugMode.buildConfig = true //设置为 true 表示导出一个 DebugModeBuildConfig.java 文件,不写默认为 true
1、因为有些 module 的代码只有 kotlin 代码,导致 debugMode 无法生效,设置为true可插入一个 java 代码即可生效,如果不需要,可以设置为 false,但需要你手动创建一个 java 代码
2、如果 debugMode 无法生效,可考虑关闭此项配置,添加设置android.defaults.buildfeatures.buildconfig=true
📌 其他配置(选填,追求极致可以配置这项)
在 根目录 的 gradle.properties 添加如下设置
androidAop.reflectInvokeMethod = true //设置为 true 反射执行切面方法 ,不写默认 false
androidAop.reflectInvokeMethod.variantOnlyDebug = true // 设置为 true 则只会在 debug 下才有效,不写默认false
androidAop.reflectInvokeMethod.static = true // 设置为 true 模拟了非反射的情况,不写默认true
1、
androidAop.reflectInvokeMethod设置为 true 反射执行切面方法会加快打包速度,设置为 false 二次编译速度和开启反射速度是基本一样的
2、请注意androidAop.reflectInvokeMethod.variantOnlyDebug设置为 true 时 release 包会忽略androidAop.reflectInvokeMethod = true的设置自动不走反射,设为 false 时则没有这种效果(不写默认false)
3、androidAop.reflectInvokeMethod.static设置为 true 模拟了非反射的情况兼顾了反射的编译速度,不写默认true,如果想使用反射建议设置此项为 true。设置为 false 则为纯反射
4、androidAop.reflectInvokeMethod.variantOnlyDebug只对 Android 的 Library 有效,对 Java 或 Kotlin 的 Library 无效
📌 CleanKeepAopCache
当你想要 clean 项目的时候,可以使用这个命令,方便 clean 后使编译时间减少一些
-
在命令行中使用
./gradlew aaaCleanKeepAopCache -
双击命令
如果找不到 aaaCleanKeepAopCache 命令,你需要在 根目录 的 build.gradle 添加如下设置
apply plugin: 'android.aop.clean'
本库内置了一些功能注解可供你直接使用
| 注解名称 | 参数说明 | 功能说明 |
|---|---|---|
| @SingleClick | value = 快速点击的间隔,默认1000ms | 单击注解,加入此注解,可使你的方法只有单击时才可进入 |
| @DoubleClick | value = 两次点击的最大用时,默认300ms | 双击注解,加入此注解,可使你的方法双击时才可进入 |
| @IOThread | ThreadType = 线程类型 | 切换到子线程的操作,加入此注解可使你的方法内的代码切换到子线程执行 |
| @MainThread | 无参数 | 切换到主线程的操作,加入此注解可使你的方法内的代码切换到主线程执行 |
| @OnLifecycle* | value = Lifecycle.Event | 监听生命周期的操作,加入此注解可使你的方法内的代码在对应生命周期内才去执行 |
| @TryCatch | value = 你自定义加的一个flag | 加入此注解可为您的方法包裹一层 try catch 代码 |
| @Permission* | tag = 自定义标记 value = 权限的字符串数组 | 申请权限的操作,加入此注解可使您的代码在获取权限后才执行 |
| @Scheduled | initialDelay = 延迟开始时间 interval = 间隔 repeatCount = 重复次数 isOnMainThread = 是否主线程 id = 唯一标识 | 定时任务,加入此注解,可使你的方法每隔一段时间执行一次,调用AndroidAop.shutdownNow(id)或AndroidAop.shutdown(id)可停止 |
| @Delay | delay = 延迟时间 isOnMainThread = 是否主线程 id = 唯一标识 | 延迟任务,加入此注解,可使你的方法延迟一段时间执行,调用AndroidAop.shutdownNow(id)或AndroidAop.shutdown(id)可取消 |
| @CheckNetwork | tag = 自定义标记 toastText = 无网络时toast提示 invokeListener = 是否接管检查网络逻辑 | 检查网络是否可用,加入此注解可使你的方法在有网络才可进去 |
| @CustomIntercept | value = 你自定义加的一个字符串数组的flag | 自定义拦截,配合 AndroidAop.setOnCustomInterceptListener 使用,属于万金油 |
( * 支持 suspend 函数,达到条件时返回结果,并支持返回类型不是 Unit 类型的suspend函数)
自定义切面
本库通过以下五种注解,实现自定义切面
- @AndroidAopPointCut 是为方法加注解的切面
- @AndroidAopMatchClassMethod 是匹配类的方法的切面
- @AndroidAopReplaceClass 是替换方法调用的
- @AndroidAopModifyExtendsClass 是修改继承类
- @AndroidAopCollectMethod 是收集继承类
一、@AndroidAopPointCut 是在方法上通过注解的形式做切面的,上述中注解都是通过这个做的,详细使用请看wiki文档
下面以 @CustomIntercept 为例介绍下该如何使用
- 创建注解(将 @AndroidAopPointCut 加到你的注解上)
@AndroidAopPointCut(CustomInterceptCut.class)
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomIntercept {
String[] value() default {};
}
- 创建注解处理切面的类(需要实现 BasePointCut 接口,它的泛型填上边的注解)
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(){
}
二、@AndroidAopMatchClassMethod 是做匹配某类及其对应方法的切面的
匹配方法支持精准匹配,点此看wiki详细使用文档
- 例子一
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进去,type 类型为 SELF 时,targetClassName 所设置的类必须是安装包里的代码。例如:Toast 这个类在 android.jar 里边是不行的
- 例子二
假如想 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 的配置
三、@AndroidAopReplaceClass 是做替换方法调用的
@AndroidAopReplaceClass 和 @AndroidAopReplaceMethod 配合使用
- 注意这种方式和前两种的有着本质的区别,前两种关注的是方法的执行,并且会自动保留可以执行原有逻辑的方法(即ProceedJoinPoint);
- 这个关注的是方法的调用,是将所有调用的地方替换为您设置的类的静态方法,并且不会自动保留执行原有逻辑的方法
- 这个方式的优点在于“相当于”可以监测到某些系统方法(android.jar里的代码)的调用,前两者不具备这个特点,所以如果不是基于此种需求,建议使用 @AndroidAopMatchClassMethod
替换方法调用详细使用方法,点此看wiki详细使用文档
- Java写法
@AndroidAopReplaceClass(
"android.widget.Toast"
)
public class ReplaceToast {
@AndroidAopReplaceMethod(
"android.widget.Toast makeText(android.content.Context, java.lang.CharSequence, int)"
)
// 因为被替换方法是静态的,所以参数类型及顺序和被替换方法一一对应
public static Toast makeText(Context context, CharSequence text, int duration) {
return Toast.makeText(context, "ReplaceToast-"+text, duration);
}
@AndroidAopReplaceMethod(
"void setGravity(int , int , int )"
)
// 因为被替换方法不是静态方法,所以参数第一个是被替换类,之后的参数和被替换方法一一对应
public static void setGravity(Toast toast,int gravity, int xOffset, int yOffset) {
toast.setGravity(Gravity.CENTER, xOffset, yOffset);
}
@AndroidAopReplaceMethod(
"void show()"
)
// 虽然被替换方法没有参数,但因为它不是静态方法,所以第一个参数仍然是被替换类
public static void show(Toast toast) {
toast.show();
}
}
该例意思就是凡是代码中写Toast.makeText和Toast.show ...的地方都被替换成ReplaceToast.makeText和ReplaceToast.show ...
- Kotlin写法
@AndroidAopReplaceClass("android.util.Log")
object ReplaceLog {
@AndroidAopReplaceMethod("int e(java.lang.String,java.lang.String)")
@JvmStatic
fun e( tag:String, msg:String) :Int{
return Log.e(tag, "ReplaceLog-$msg")
}
}
该例意思就是凡是代码中写Log.e的地方都被替换成ReplaceLog.e
四、@AndroidAopModifyExtendsClass 是修改目标类的继承类详细使用方式
通常是在某个类的继承关系中替换掉其中一层,然后重写一些函数,在重写的函数中加入一些你想加的逻辑代码,起到监听、改写原有逻辑的作用
如下例所示,就是要把 AppCompatImageView 的继承类替换成 ReplaceImageView
应用场景:非侵入式地实现监控大图加载的功能
@AndroidAopModifyExtendsClass("androidx.appcompat.widget.AppCompatImageView")
public class ReplaceImageView extends ImageView {
public ReplaceImageView(@NonNull Context context) {
super(context);
}
public ReplaceImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public ReplaceImageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void setImageDrawable(@Nullable Drawable drawable) {
super.setImageDrawable(drawable);
//做一些监测或者再次修改
}
}
五、@AndroidAopCollectMethod 是收集继承类详细使用方式
使用起来极其简单,示例代码已经说明了
- Kotlin
object InitCollect {
private val collects = mutableListOf<SubApplication>()
private val collectClazz: MutableList<Class<out SubApplication>> = mutableListOf()
@AndroidAopCollectMethod
@JvmStatic
fun collect(sub: SubApplication){
collects.add(sub)
}
@AndroidAopCollectMethod
@JvmStatic
fun collect2(sub:Class<out SubApplication>){
collectClazz.add(sub)
}
//直接调这个方法(方法名不限)上边的函数会被悉数回调
fun init(application: Application){
for (collect in collects) {
collect.onCreate(application)
}
}
}
- Java
public class InitCollect2 {
private static final List<SubApplication2> collects = new ArrayList<>();
private static final List<Class<? extends SubApplication2>> collectClazz = new ArrayList<>();
@AndroidAopCollectMethod
public static void collect(SubApplication2 sub){
collects.add(sub);
}
@AndroidAopCollectMethod
public static void collect3(Class<? extends SubApplication2> sub){
collectClazz.add(sub);
}
//直接调这个方法(方法名不限)上边的函数会被悉数回调
public static void init(Application application){
Log.e("InitCollect2","----init----");
for (SubApplication2 collect : collects) {
collect.onCreate(application);
}
}
}
至此 AndroidAOP 的使用介绍就结束了,还等什么?你的项目正在召唤你~