[Android 开发]推荐一个专属 Android 端 AOP 切面框架,没有 AspectJ,只需一个注解就可以请求权限、切换线程、禁止多点、监测生命周期等等,甚至可以切入三方库 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tianxiangyu1688
V2EX    Android

[Android 开发]推荐一个专属 Android 端 AOP 切面框架,没有 AspectJ,只需一个注解就可以请求权限、切换线程、禁止多点、监测生命周期等等,甚至可以切入三方库

  •  
  •   tianxiangyu1688 2023-12-14 15:11:49 +08:00 9986 次点击
    这是一个创建于 748 天前主题,其中的信息可能已经有所发展或是发生改变。

    AndroidAOP 是专属于 Android 端 Aop 框架,只需一个注解就可以请求权限、切换线程、禁止多点、监测生命周期等等,本库不是基于 AspectJ 实现的 Aop,当然你也可以定制出属于你的 Aop 代码

    AndroidAOP - Github 链接

    特色功能

    1 、本库内置了开发中常用的一些切面注解供你使用

    2 、本库支持让你自己做切面,语法简单易上手

    3 、本库同步支持 Java 和 Kotlin 代码

    4 、本库支持切入三方库

    5 、本库支持切点方法为 Lambda 表达式的情况

    6 、本库支持生成所有切点信息 Json 文件,方便一览所有切点位置在此配置

    7 、本库不是基于 AspectJ 实现的,织入代码量极少,侵入性极低

    点此下载 apk

    版本限制

    最低 Gradle 版本:8.0

    最低 SDK 版本:minSdkVersion >= 21

    使用步骤

    在开始之前可以给项目一个 Star 吗?非常感谢,你的支持是我唯一的动力。欢迎 Star 和 Issues!

    一、在项目根目录下的 build.gradle 添加(必须)

    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 } 

    Kotlin 和 KSP Github 的匹配版本号列表

    二、在 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.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 上

    四、在 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 } 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 使用,属于万金油

    上述注解使用示例都在这,还有这

    这块强调一下 @OnLifecycle

    • 1 、 @OnLifecycle 加到的方法所属对象必须是属于直接或间接继承自 FragmentActivity 或 Fragment 的方法才有用,或者注解方法的对象实现 LifecycleOwner 也可以
    • 2 、如果第 1 点不符合的情况下,可以给切面方法第一个参数设置为第 1 点的类型,在调用切面方法传入也是可以的,例如:
    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 使用此注解你可以设置以下设置(非必须)
    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; } }); 
    • @Permission 使用此注解必须配合以下设置(此步为必须设置的,否则是没效果的)
    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] 就可以拿到 } } }); 
    • @CustomIntercept 使用此注解你必须配合以下设置(此步为必须设置的,否则还有什么意义呢?)
    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 和 @AndroidAopMatchClassMethod 两种注解,实现自定义切面

    一、**@AndroidAopPointCut** 是在方法上通过注解的形式做切面的,上述中注解都通过这个做的,详细使用请看 wiki 文档

    注意:自定义的注解(也就是被 @AndroidAopPointCut 注解的注解类)如果是 Kotlin 代码请用 android-aop-ksp 那个库

    下面以 @CustomIntercept 为例介绍下该如何使用

    • 创建注解
    @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 详细使用文档

    注意:自定义的匹配类方法切面(也就是被 @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 的配置

    详细使用请看 wiki 文档

    常见问题

    1 、Build 时报错 "ZipFile invalid LOC header (bad signature)"

    • 请重启 Android Studio ,然后 clean 项目

    2 、 同一个方法存在多个注解或匹配切面时,怎么处理的

    • 多个切面叠加到一个方法上时注解优先于匹配切面(上文的匹配切面),注解切面之间从上到下依次执行
    • 调用 proceed 才会执行下一个切面,多个切面中最后一个切面执行 proceed 才会调用切入方法内的代码
    • 在前边切面中调用 proceed(args) 可更新方法传入参数,并在下一个切面中也会拿到上一层更新的参数
    • 存在异步调用proceed时,第一个异步调用 proceed 切面的返回值(就是 invoke 的返回值)就是切入方法的返回值;

    混淆规则

    下边是涉及到本库的一些必须混淆规则

    # 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----- 
    1 条回复    2024-02-06 17:31:34 +08:00
    iflint
        1
    iflint  
       2024-02-06 17:31:34 +08:00
    去看看
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2300 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 05:17 PVG 13:17 LAX 21:17 JFK 00:17
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86