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'
}
相关文章:

Android AOP:aspectjx
加入引用 在整个项目的 build.gradle 中,添加 classpath "com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10" 可以看到测试demo的 gradle 版本是很低的。 基于 github 上的文档,可以看到原版只支持到 gradle 4.4 。后续需要使…...

前端【11】HTML+CSS+jQUery实战项目--实现一个简单的todolist
前端【8】HTMLCSSjavascript实战项目----实现一个简单的待办事项列表 (To-Do List)-CSDN博客 学过jQUery可以极大简化js代码的编写,基于之前实现的todolist小demo,了解如何使用 jQuery 来实现常见的动态交互功能。 修改后的js代码 关键点解析 动态添加…...

2025课题推荐——USBL与DVL数据融合的实时定位系统
准确的定位技术是现代海洋探测、海洋工程和水下机器人操作的基础。超短基线(USBL)和多普勒速度计(DVL)是常用的水下定位技术,但单一技术难以应对复杂环境。因此,USBL与DVL的数据融合以构建实时定位系统&…...

滑动窗口详解:解决无重复字符的最长子串问题
滑动窗口详解:解决无重复字符的最长子串问题 在算法面试中,“无重复字符的最长子串”问题是一个经典题目,不仅考察基础数据结构的运用,还能够反映你的逻辑思维能力。而在解决这个问题时,滑动窗口(Sliding …...

第05章 11 动量剖面可视化代码一则
在计算流体力学(CFD)中,动量剖面(Momentum Profiles)通常用于描述流体在流动方向上的动量分布。在 VTK 中,可以通过读取速度场数据,并计算和展示动量剖面来可视化呈现速度场信息。 示例代码 以…...

MySQL的复制
一、概述 1.复制解决的问题是让一台服务器的数据与其他服务器保持同步,即主库的数据可以同步到多台备库上,备库也可以配置成另外一台服务器的主库。这种操作一般不会增加主库的开销,主要是启用二进制日志带来的开销。 2.两种复制方式…...

Cpp::IO流(37)
文章目录 前言一、C语言的输入与输出二、什么是流?三、C IO流C标准IO流C文件IO流以写方式打开文件以读方式打开文件 四、stringstream的简单介绍总结 前言 芜湖,要结束喽! 一、C语言的输入与输出 C语言中我们用到的最频繁的输入输出方式就是 …...

基于OpenCV实现的答题卡自动判卷系统
一、图像预处理 🌄 二、查找答题卡轮廓 📏 三、透视变换 🔄 四、判卷与评分 🎯 五、主函数 六、完整代码+测试图像集 总结 🌟 在这篇博客中,我将分享如何使用Python结合OpenCV库开发一个答题卡自动判卷系统。这个系统能够自动从扫描的答题卡中提取信…...

如何将电脑桌面默认的C盘设置到D盘?详细操作步骤!
将电脑桌面默认的C盘设置到D盘的详细操作步骤! 本博文介绍如何将电脑桌面(默认为C盘)设置在D盘下。 首先,在D盘建立文件夹Desktop,完整的路径为D:\Desktop。winR,输入Regedit命令。(或者单击【…...

二十三种设计模式-享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享相同对象来减少内存使用,尤其适合在大量重复对象的情况下。 核心概念 享元模式的核心思想是将对象的**可共享部分(内部状态)提取出来进行共…...

算法【有依赖的背包】
有依赖的背包是指多个物品变成一个复合物品(互斥),每件复合物品不要和怎么要多种可能性展开。时间复杂度O(物品个数 * 背包容量),额外空间复杂度O(背包容量)。 下面通过题目加深理解。 题目一 测试链接:[NOIP2006 提…...

A7. Jenkins Pipeline自动化构建过程,可灵活配置多项目、多模块服务实战
服务容器化构建的环境配置构建前需要解决什么下面我们带着问题分析构建的过程:1. 如何解决jenkins执行环境与shell脚本执行环境不一致问题?2. 构建之前动态修改项目的环境变量3. 在通过容器打包时避免不了会产生比较多的不可用的镜像资源,这些资源要是不及时删除掉时会导致服…...

飞牛NAS新增虚拟机功能,如果使用虚拟机网卡直通安装ikuai软路由(如何解决OVS网桥绑定失败以及打开ovs后无法访问飞牛nas等问题)
文章目录 📖 介绍 📖🏡 演示环境 🏡📒 飞牛NAS虚拟机安装爱快教程 📒🛠️ 前期准备🌐 网络要求💾 下载爱快镜像🚀 开始安装💻 开启IOMMU直通🌐 配置网络🚨 解决OVS网桥绑定失败以及打开ovs后无法访问飞牛nas等问题➕ 创建虚拟机🎯 安装ikuai💻 进…...

蓝桥杯例题四
每个人都有无限潜能,只要你敢于去追求,你就能超越自己,实现梦想。人生的道路上会有困难和挑战,但这些都是成长的机会。不要被过去的失败所束缚,要相信自己的能力,坚持不懈地努力奋斗。成功需要付出汗水和努…...

八股——Java基础(四)
目录 一、泛型 1. Java中的泛型是什么 ? 2. 使用泛型的好处是什么? 3. Java泛型的原理是什么 ? 什么是类型擦除 ? 4.什么是泛型中的限定通配符和非限定通配符 ? 5. List和List 之间有什么区别 ? 6. 可以把List传递给一个接受List参数的方法吗? 7. Arra…...

CVE-2023-38831 漏洞复现:win10 压缩包挂马攻击剖析
目录 前言 漏洞介绍 漏洞原理 产生条件 影响范围 防御措施 复现步骤 环境准备 具体操作 前言 在网络安全这片没有硝烟的战场上,新型漏洞如同隐匿的暗箭,时刻威胁着我们的数字生活。其中,CVE - 2023 - 38831 这个关联 Win10 压缩包挂…...

【自然语言处理(NLP)】深度循环神经网络(Deep Recurrent Neural Network,DRNN)原理和实现
文章目录 介绍深度循环神经网络(DRNN)原理和实现结构特点工作原理符号含义公式含义 应用领域优势与挑战DRNN 代码实现 个人主页:道友老李 欢迎加入社区:道友老李的学习社区 介绍 **自然语言处理(Natural Language Pr…...

Linux 命令之技巧(Tips for Linux Commands)
Linux 命令之技巧 简介 Linux 是一种免费使用和自由传播的类Unix操作系统,其内核由林纳斯本纳第克特托瓦兹(Linus Benedict Torvalds)于1991年10月5日首次发布。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户…...

【文星索引】搜索引擎项目测试报告
目录 一、项目背景二、 项目功能2.1 数据收集与索引2.2 API搜索功能2.3 用户体验与界面设计2.4 性能优化与维护 三、测试报告3.1 功能测试3.2 界面测试3.3 性能测试3.4 兼容性测试3.5 自动化测试 四、测试总结4.1 功能测试方面4.2 性能测试方面4.3 用户界面测试方面 一、项目背…...

低代码系统-产品架构案例介绍、轻流(九)
轻流低代码产品定位为零代码产品,试图通过搭建来降低企业成本,提升业务上线效率。 依旧是从下至上,从左至右的顺序 名词概述运维层底层系统运维层,例如上线、部署等基础服务体系内置的系统能力,发消息、组织和权限是必…...

二叉树(补充)
二叉树 1.二叉树的基本特性2.堆2.1.堆的基本概念2.2.堆的实现2.2.1.基本结构2.2.2.堆的初始化 2.2.3.堆的销毁2.2.4.堆的插入2.2.5.取出堆顶的数据2.2.6.堆的删除2.2.7.堆的判空2.2.8.堆的数据个数2.2.9.交换2.2.10.打印堆数据2.2.11.堆的创建2.2.12.堆排序 1.二叉树的基本特性…...

(DM)达梦数据库基本操作(持续更新)
1、连接达梦数据库 ./disql 用户明/"密码"IP端口或者域名 2、进入某个模式(数据库,因达梦数据库没有库的概念,只有模式,可以将模式等同于库) set schema 库名; 3、查表结构; SELECT COLUMN_NAM…...

CRM 微服务
文章目录 项目地址一、项目地址 教程作者:教程地址:代码仓库地址:所用到的框架和插件:dbt airflow一、 用户与认证服务 主要功能: 用户注册、登录、注销。 认证(OAuth、JWT 等)。 权限和角色管理(RBAC/ABAC)。 单点登录(SSO)。 技术亮点: 集成第三方身份认证(如 …...

AI软件外包需要注意什么 外包开发AI软件的关键因素是什么 如何选择AI外包开发语言
1. 定义目标与需求 首先,要明确你希望AI智能体做什么。是自动化任务、数据分析、自然语言处理,还是其他功能?明确目标可以帮助你选择合适的技术和方法。 2. 选择开发平台与工具 开发AI智能体的软件时,你需要选择适合的编程语言、…...

DBSyncer开源数据同步中间件
一、简介 DBSyncer(英[dbsɪŋkɜː(r)],美[dbsɪŋkɜː(r) 简称dbs)是一款开源的数据同步中间件,提供MySQL、Oracle、SqlServer、PostgreSQL、Elasticsearch(ES)、Kafka、File、SQL等同步场景。支持上传插件自定义同步转换业务,提供监控全量…...

< OS 有关 > 阿里云 几个小时前 使用密钥替换 SSH 密码认证后, 发现主机正在被“攻击” 分析与应对
信息来源: 文件:/var/log/auth.log 因为在 sshd_config 配置文件中,已经定义 LogLevel INFO 部分内容: 2025-01-27T18:18:55.68272708:00 jpn sshd[15891]: Received disconnect from 45.194.37.171 port 58954:11: Bye Bye […...

react-bn-面试
1.主要内容 工作台待办 实现思路: 1,待办list由后端返回,固定需要的字段有id(查详细)、type(本条待办的类型),还可能需要时间,状态等 2,一个集中处理待办中转路由页,所有待办都跳转到这个页面…...

1. Java-MarkDown文件创建-工具类
Java-MarkDown文件创建-工具类 1. 思路 根据markdown语法,拼装markdown文本内容 2. 工具类 import java.util.Arrays; import java.util.List;/*** Markdown生成工具类* Author: 20004855* Date: 2021/1/15 16:00*/ public class MarkdownGenerator {private Str…...

全连接神经网络(前馈神经网络)
一、全连接神经网络介绍 在多层神经网络中, 第 N 层的每个神经元都分别与第 N-1 层的神经元相互连接。 1、神经元 这个神经元接收的输入信号为向量 , 向量为输入向量的组合权重, 为偏置项, 是一个标量。 神经元的作用是对输入向…...

【llm对话系统】什么是 LLM?大语言模型新手入门指南
什么是 LLM?大语言模型新手入门指南 大家好!欢迎来到 LLM 的奇妙世界!如果你对人工智能 (AI) 的最新进展,特别是那些能像人类一样阅读、写作甚至进行对话的 AI 感兴趣,那么你来对地方了。这篇文章将带你认识 LLM 的基…...