Android AOP:aspectjx

加入引用

在整个项目的 build.gradle 中,添加 

classpath "com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10"

 可以看到测试demo的 gradle 版本是很低的。

基于 github 上的文档,可以看到原版只支持到 gradle 4.4 。后续需要使用社区版的 aspectjx

GitHub - HujiangTechnology/gradle_plugin_android_aspectjx: A Android gradle plugin that effects AspectJ on Android project and can hook methods in Kotlin, aar and jar file.

然后在App 目录下的 build.gradle 中加入plugin 标记即可。

apply plugin: 'android-aspectjx'

还可以指定需要生效的位置,块放在最后即可。

include 生效的包名

exclude 排除的包名


使用

基于 @Aspect 注解,LogAspect  不需要在任何地方调用,自动会织入。

package com.aaaa.testplayer;import android.util.Log;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;/*** 切入点学习:* 所有类切入点:  (* com.aaaa.testplayer..*(..))* 1. execution(...):* 这是一个切点表达式,用于匹配方法执行的连接点。* <p>* 2. * (返回值类型):* 这个星号表示“任意返回类型”。也就是说,这个切点将匹配任何返回类型的方法,无论是 void、int、String 等。* <p>* 3. com.aaaa.testplayer..* (声明类型模式):* com.aaaa.testplayer: 这是你的包名。* ..: 这个符号表示“零个或多个子包”。它允许在 com.aaaa.testplayer 包及其所有子包中匹配类。例如,它会匹配:* com.aaaa.testplayer.MyClass* com.aaaa.testplayer.subpackage.AnotherClass* *: 这个星号表示“任意类名”。它允许匹配该包下的所有类。* <p>* 4. ( .. ) (参数模式):* .. 代表“零个或多个参数”。这意味着该切点会匹配任何数量的参数,包括没有参数的情况。比如:* myMethod()* myMethod(int a)* myMethod(String a, int b, double c)* <p>* <p>* 另一种实现:** @Pointcut("execution(* com.aaaa.testplayer.*.*(..))")* 匹配的是 com.example.service 包下的所有类和方法。* *.*(..) 的意思是匹配该包下任何类中的任何方法,不论返回类型、方法名称或参数类型。*//**** JoinPoint :* 1. 获取上下文信息:通过 JoinPoint,你可以获取关于当前连接点的信息,比如方法名、类名、参数等等。* 2. 不能控制流程:JoinPoint 只能用来查看信息,而不能控制方法的执行。** ProceedingJoinPoint:* 1. 控制方法执行:你可以使用 proceed() 方法来继续执行原始方法,或者选择不执行它。* 2. 获取上下文信息:像 JoinPoint 一样,ProceedingJoinPoint 也可以访问连接点的信息,但它还额外提供了一些可以控制的功能。*** JoinPoint 的使用,具体可以参考:beforeAdvice** ProceedingJoinPoint主要多了 proceed()方法,配合 @Around 环绕,可以控制方法是否执行** *//*** 无法动态修改已编译的系统类!!!** 关于 throws Throwable 。可以使用 try/catch 组合来代替,主要取决于是否要向上传递 error* *//*** @Before: 在目标方法执行之前。* @After: 在目标方法执行之后,无论结果如何。* @AfterReturning: 在目标方法成功返回之后。* @AfterThrowing: 在目标方法抛出异常之后。* @Around: 可自定义目标方法的执行过程,包括前置和后置操作。* */
@Aspect
public class LogAspect {/*** @Before 在指定位置之前进行触发* ************************************************************************/// 在指定的 class 方法运行前,进行log输出
//    @Before("execution(* com.aaaa.testplayer.MainActivity.onCreate(..))")
//    public void beforeOnCreate() {
//        Log.e("aaaaa","MainActivity onCreate called");
//    }/*** 切入点学习:*  1. * android.util.Log.d(String, String):*      * 表示“任意返回类型”,在这里它表示 Log.d 方法可以是任何返回类型(由于 Log.d 返回 int 作为日志行 ID,但我们通常不关心返回值)。**  2. android.util.Log.d: 这是我们要匹配的具体方法,即 Android 的 Log 类的 d 方法,表示 "debug" 日志。*      (String, String): 表示该方法接受两个 String 类型的参数。也就是说,它只会匹配传递两个 String 参数的 Log.d 方法调用。**  3.&& args(tag, msg):*      && 是逻辑与操作符,表示该切点表达式的两个部分必须同时满足。*      args(tag, msg): 这个部分用于提取通过方法参数传入的数据。tag 和 msg 是这两个参数的名称,可以在通知方法中使用。这允许你在切面中引用这些参数,使得你能够对子日志信息进行处理或记录。**   如果 不加 && args(tag, msg),那么下面就无法获取整个参数!!!* */// 在指定的方法运行前,进行log输出
//    @Before("call(* android.util.Log.d(String, String)) && args(tag, msg)")
//    public void replaceLog(String tag, String msg) {
//        // 将所有 Log.d 前增加为 Log.e
//        Log.e(tag, "[Replaced] " + msg);
//    }// 在所有方法执行前被调用,并打印出正在执行的方法名称。
//    @Before("execution(* com.aaaa.testplayer..*(..))")
//    public void beforeAdvice(JoinPoint joinPoint) {
//        // 获取方法名
//        String methodName = joinPoint.getSignature().getName();
//        // 可以用来获得当前执行的方法所在的类的全名
//        String methodDeclaringTypeName = joinPoint.getSignature().getDeclaringTypeName();
//        // 用于在运行时获取方法的声明类对象,从而可以调用该类的方法、访问其字段等。
//        Class<?> declaringType = joinPoint.getSignature().getDeclaringType();
//        // 注意这里因为劫持了所有方法所以要检查 methodName, 不然会死循环
//        if (declaringType == MainActivity.class && methodName == "onCreate") {
//            MainActivity mainActivity = (MainActivity) joinPoint.getTarget(); // 获取目标对象
//            try {
//                // 调用 showMsg 方法
//                mainActivity.showMsg("This is a message from the aspect!");
//            } catch (Exception e) {
//                Log.e("AspectError", "Error calling showMsg method", e);
//            }
//        }
//
//        // 获取目标对象
//        Object targetObject = joinPoint.getTarget();
//
//        // 获取代理对象
//        Object proxyObject = joinPoint.getThis();
//
//        // 获取参数
//        // 可以获取到入参,例如在 public void onTest1(View view) 中可以获取到入参  android.widget.Button{f2aba96 VFED..C.. .F....ID 0,0-300,90}
//        Object[] args = joinPoint.getArgs();
//
//        // 打印信息到 Logcat
//        Log.e("aaaaa", "Method name: " + methodName);
//        Log.e("aaaaa", "methodDeclaringTypeName name: " + methodDeclaringTypeName);
//        Log.e("aaaaa", "目标对象 Target object: " + targetObject.getClass().getSimpleName());
//        Log.e("aaaaa", "代理对象 Proxy object: " + proxyObject.getClass().getSimpleName());
//        Log.e("aaaaa", "Arguments: " + Arrays.toString(args));
//        if (args.length > 0) {
//            for (Object arg : args) {
//                if (arg == null){
//                    continue;
//                }
//                Log.e("Aspect", "Argument type: " + arg.getClass().getSimpleName() + ", value: " + arg);
//            }
//        }
//
//        // 连接点描述
//        Log.e("aaaaa", ("JoinPoint description: " + joinPoint.toString()));
//
//        // 获取源位置(文件名和行号)
//        SourceLocation location = joinPoint.getSourceLocation();
//        Log.e("aaaaa", "Source location: " + location.getFileName() + ":" + location.getLine());
//    }/*** @Around 在运行中触发* ************************************************************************//*** 1. @Around 通知* 允许你完全控制目标方法的执行,包括是否调用原方法(通过 joinPoint.proceed())。** 2.不调用 proceed()* 这里我们直接返回 Log.e 的结果,不执行 joinPoint.proceed(),从而完全跳过原始 Log.d 的执行。** 3.返回值处理* Log.d 和 Log.e 都返回 int(日志的优先级/类型),因此直接返回 Log.e 的返回值是类型安全的。如果调用代码依赖返回值,也能保持一致。** 4.参数注入* 通过 args(tag, msg) 将原始参数注入方法,你可以在新逻辑中复用或修改它们。** @Around 在性能上与 @Before 差异不大,但避免了冗余的原方法调用。* */// 将全局的log.d 调用替换为 Log.e@Around("call(* android.util.Log.d(String, String)) && args(tag, msg)")public Object replaceLog(ProceedingJoinPoint joinPoint, String tag, String msg) throws Throwable {// 方法执行前的逻辑
//        System.out.println("Before method: " + joinPoint.getSignature().getName());// 这里决定了是否要调用原有的参数。如果调用这句,约等于前面的 before ,不过会先打出log.d
//        joinPoint.proceed();// 方法执行后的逻辑
//        System.out.println("After method: " + joinPoint.getSignature().getName());// 直接调用 Log.e 替换 Log.d,并阻止原方法执行return Log.e(tag, "[Replaced] " + msg);}/*** @After* 目标方法执行后执行,无论该方法是否抛出异常。能够用于执行一些清理工作、记录日志等* 无法直接获取方法的返回值,没有 JoinPoint* ************************************************************************************ */// Log 方法调用后输出日志。 这里用 println 避免死循环
//    @After("call(* android.util.Log.*(String, String))")
//    public void afterAdvice() {
//        System.out.println("Log method execution");
//    }/*** @AfterReturning* 在目标方法成功返回后执行,可以获取返回值。适合用于对成功结果的处理。* ************************************************************************************ */// 指定方法运行完成后获取返回值
//    @AfterReturning(pointcut = "execution(* com.aaaa.testplayer.MainActivity.getMsg())", returning = "result")
//    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
//        Log.e("aaaaa","AfterReturning Method executed: " + joinPoint.getSignature().getName());
//        Log.e("aaaaa","AfterReturning Returned value: " + result);
//
//        // 获取方法参数
//        Object[] args = joinPoint.getArgs();
//        System.out.println("MAfterReturning ethod arguments: ");
//        for (Object arg : args) {
//            Log.e("aaaaa","AfterReturning Argument: " + arg);
//        }
//    }/*** @AfterThrowing* 当目标方法抛出异常时执行。可以用来进行异常处理或日志记录。* ************************************************************************************ */// 定义切点,匹配所有抛出异常的方法
//    @AfterThrowing(pointcut = "execution(* com.aaaa.testplayer..*(..))", throwing = "ex")
//    public void logAfterThrowing(Exception ex) {
//        Log.e("aaaaa", "发现exception !!! " + ex.getMessage());
//        // 这里可以添加其他处理逻辑,比如发送通知、记录到数据库等
//    }
}

反编译后可以看到对应的位置前新增了代码 

多个切点的处理:

可以预先定义 Pointcut ,也可以对定义的 Pointcut 进行复用。


关于第三方库 

aspectjx 可以对引入的三方库也生效,但是对已经编译好的 Android 系统类(例如 TextView)无法进行动态修改。这里使用三方库 autosize 来示例。

因为 autosize 在部分设备上显示有问题,需要调试。autosize 库默认对日志进行 debug 级别输出,但是在某些设备上,系统只输出 info 级别及以上的 log,导致 autosize 日志全无,这里借助工具将 me.jessyan.autosize.utils.AutoSizeLog 中的日志级别强制提到 error 级。

首先需要引入三方库:

然后在 aspectx 中记得加入对应的包名,不然织入不会生效。

编写替换代码,这里将全局的 Log.d 都替换为 Log.e 了。这个代码上面有示例

运行后即可看到日志都被提升到 error 

反编译apk查看 ,可以看到 debug 级别输出的代码已经被替换。 warn 和 err 不受影响


关于高 gradle 版本 

根据github 官方文档,gradle 支持只到 4.4 。那么高 gradle 需要额外处理:

强制使用 gradle 7.1.3

最小的改动就是将 gradl 版本指定为 7.1.3 .之前我用的 7.2.2 不行会报

Failed to apply plugin ‘android-aspectjx’. No such property: FD_INTERMEDIATES for class: com.android.builder.model.AndroidProject

gradle7.2及以上可使用wurensen重构过的版本来实现AOP操作。

参考:GitHub - LZ9/AspectjxDemo: 简单的Aspectjx接入demo,兼容gradle7以上版本 

classpath 'io.github.wurensen:gradle-android-plugin-aspectjx:3.3.2' 

 

apply plugin: 'io.github.wurensen.android-aspectjx' 

aspectjx {enabled trueinclude 'com.aaaa.testplayer'exclude 'androidx', 'com.google', 'com.squareup', 'com.alipay', 'com.taobao', 'org.apache','kotlinx', 'org.jetbrains', "module-info", 'versions.9'
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/67176.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第84期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

TCP/IP 协议:互联网通信的基石

TCP/IP 协议:互联网通信的基石 引言 TCP/IP协议,全称为传输控制协议/互联网协议,是互联网上应用最为广泛的通信协议。它定义了数据如何在网络上传输,是构建现代互联网的基础。本文将深入探讨TCP/IP协议的原理、结构、应用以及其在互联网通信中的重要性。 TCP/IP 协议概述…

蛇年特别版贪吃蛇H5小游戏

该作者的原创文章目录: 生产制造执行MES系统的需求设计和实现 企业后勤管理系统的需求设计和实现 行政办公管理系统的需求设计和实现 人力资源管理HR系统的需求设计和实现 企业财务管理系统的需求设计和实现 董事会办公管理系统的需求设计和实现 公司组织架构图设计工具 库存管…

MapReduce,Yarn,Spark理解与执行流程

MapReduce的API理解 Mapper 如果是单词计数&#xff1a;hello&#xff1a;1&#xff0c; hello&#xff1a;1&#xff0c; world&#xff1a;1 public void map(Object key, // 首字符偏移量Text value, // 文件的一行内容Context context) // Mapper端的上下文&#xff0c;…

如何将xps文件转换为txt文件?xps转为pdf,pdf转为txt,提取pdf表格并转为txt

文章目录 xps转txt方法一方法二 pdf转txt整页转txt提取pdf表格&#xff0c;并转为txt 总结另外参考XPS文件转换为TXT文件XPS文件转换为PDF文件PDF文件转换为TXT文件提取PDF表格并转为TXT示例代码&#xff08;部分&#xff09; 本文测试代码已上传&#xff0c;路径如下&#xff…

Day26-【13003】短文,什么是顺序表?顺序表和数组、内存地址的关系?顺序表的插入、删除操作如何实现?操作的时间复杂度是多少?

文章目录 第二节&#xff0c;线性表的顺序存储及实现概览什么是顺序表和链表&#xff1f;顺序存储的叫顺序表顺序表和数组还有内存地址的关系&#xff1f;顺序表的基本操作如何实现&#xff1f;1、插入操作如何实现&#xff1f;2、删除操作如何实现&#xff1f;3、赋值和查找操…

【含开题报告+文档+PPT+源码】基于SpringBoot的校园跑腿管理系统

开题报告 本文旨在探讨校园跑腿系统的设计与实现&#xff0c;通过深入研究与分析&#xff0c;实现了一套包含用户管理、发布跑腿单、跑腿抢单、跑腿单评论、在线留言以及用户在线充值等功能的综合性系统。该系统以提高校园内物品跑腿与配送效率为核心目标&#xff0c;为广大学…

zookeeper的介绍和简单使用

1 zookerper介绍 zookeeper是一个开源的分布式协调服务&#xff0c;由Apache软件基金会提供&#xff0c;主要用于解决分布式应用中的数据管理、状态同步和集群协调等问题。通过提供一个高性能、高可用的协调服务&#xff0c;帮助构建可靠的分布式系统。 Zookeeper的特点和功能…

二级 二维数组3

对角线之和 题目描述 输入一个矩阵&#xff0c;输出右上-左下对角线上的数字和 输入 输入1个整数N。(N<10)表示矩阵有n行n列 输出 对角线的和 样例 输入复制 4 1 2 3 4 2 3 4 5 4 5 6 7 1 2 3 4 输出复制 14 #include<iostream> using namespace std; int main() {i…

Spring Boot MyBatis Plus 版本兼容问题(记录)

Spring Boot & MyBatis Plus 版本兼容问题&#xff08;Invalid value type for attribute factoryBeanObjectType: java.lang.String&#xff09; 问题描述问题排查1. 检查 MapperScan 的路径2. 项目中没有配置 FactoryBean3. 检查 Spring 和 MyBatis Plus 版本兼容性 解决…

嵌入式学习笔记-杂七杂八

文章目录 连续波光纤耦合激光器工作原理主要特点应用领域设计考虑因素 数值孔径&#xff08;Numerical Aperture&#xff0c;简称NA&#xff09;数值孔径的定义数值孔径的意义数值孔径的计算示例数值孔径与光纤 四象限探测器检测目标方法四象限划分检测目标的步骤1. 数据采集2.…

Java Web-Cookie与Session

会话跟踪技术 会话跟踪技术是一种在 Web 应用程序中跟踪用户会话状态的机制&#xff0c;它允许服务器在多个请求之间识别和关联属于同一用户的请求&#xff0c;以便在整个会话过程中保持用户相关的信息。以下是几种常见的会话跟踪技术&#xff1a; Cookie 概念&#xff1a;Cook…

Spring Boot - 数据库集成04 - 集成Redis

Spring boot集成Redis 文章目录 Spring boot集成Redis一&#xff1a;redis基本集成1&#xff1a;RedisTemplate Jedis1.1&#xff1a;RedisTemplate1.2&#xff1a;实现案例1.2.1&#xff1a;依赖引入和属性配置1.2.2&#xff1a;redisConfig配置1.2.3&#xff1a;基础使用 2&…

STM32使用VScode开发

文章目录 Makefile形式创建项目新建stm项目下载stm32cubemx新建项目IED makefile保存到本地arm gcc是编译的工具链G++配置编译Cmake +vscode +MSYS2方式bilibiliMSYS2 统一环境配置mingw32-make -> makewindows环境变量Cmake CmakeListnijia 编译输出elfCMAKE_GENERATOR查询…

Oracle 12c 中的 CDB和PDB的启动和关闭

一、简介 Oracle 12c引入了多租户架构&#xff0c;允许一个容器数据库&#xff08;Container Database, CDB&#xff09;托管多个独立的可插拔数据库&#xff08;Pluggable Database, PDB&#xff09;。本文档旨在详细描述如何启动和关闭CDB及PDB。 二、容器数据库 (CDB) 2.1…

网络仿真工具Core环境搭建

目录 安装依赖包 源码下载 Core安装 FAQ 下载源码TLS出错误 问题 解决方案 找不到dbus-launch 问题 解决方案 安装依赖包 调用以下命令安装依赖包 apt-get install -y ca-certificates git sudo wget tzdata libpcap-dev libpcre3-dev \ libprotobuf-dev libxml2-de…

FPGA实现任意角度视频旋转(二)视频90度/270度无裁剪旋转

本文主要介绍如何基于FPGA实现视频的90度/270度无裁剪旋转&#xff0c;旋转效果示意图如下&#xff1a; 为了实时对比旋转效果&#xff0c;采用分屏显示进行处理&#xff0c;左边代表旋转前的视频在屏幕中的位置&#xff0c;右边代表旋转后的视频在屏幕中的位置。 分屏显示的…

JavaEE:多线程进阶

JavaEE&#xff1a;多线程进阶 一、对比不同锁策略之间的应用场景及其区别1. 悲观锁 和 乐观锁1.1 定义和原理1.2 应用场景1.3 示例代码 2. 重量级锁 和 轻量级锁2.1 定义和原理2.2 应用场景2.3 示例代码 3. 挂起等待锁 和 自旋锁3.1 定义和原理3.2 应用场景3.3 示例代码 4. 几…

董事会办公管理系统的需求设计和实现

该作者的原创文章目录&#xff1a; 生产制造执行MES系统的需求设计和实现 企业后勤管理系统的需求设计和实现 行政办公管理系统的需求设计和实现 人力资源管理HR系统的需求设计和实现 企业财务管理系统的需求设计和实现 董事会办公管理系统的需求设计和实现 公司组织架构…

pytest自动化测试 - pytest夹具的基本概念

<< 返回目录 1 pytest自动化测试 - pytest夹具的基本概念 夹具可以为测试用例提供资源(测试数据)、执行预置条件、执行后置条件&#xff0c;夹具可以是函数、类或模块&#xff0c;使用pytest.fixture装饰器进行标记。 1.1 夹具的作用范围 夹具的作用范围&#xff1a; …