一文详解Android里的AOP编程

1. 基于 AspectJ(编译期/打包期织入)

  • 思路:用 AspectJ 编译器在 编译阶段Gradle Transform 阶段,把切面逻辑织入 class / bytecode。

  • 特点

    • 能实现类似 Spring AOP 的注解切面,支持 @Around@Before@After 等。
    • 典型应用:埋点、性能监控、日志采集。
  • 集成方式

    • 使用 Hujiang/gradle_plugin_android_aspectjx(支持 Gradle 插件织入)。

    • 在切面类里写:

      @Aspect
      public class LogAspect {@Pointcut("execution(* com.example.myapp..*(..))")public void methodPointcut() {}@Before("methodPointcut()")public void beforeMethod(JoinPoint joinPoint) {Log.d("AOP", "调用方法: " + joinPoint.getSignature());}
      }
      

2. 基于 ASM / Javassist(字节码修改)

  • 思路:在 编译后 / 打包前 修改字节码,插入所需逻辑。
  • 特点
    • 更底层、更灵活,但开发成本高。
    • 一般用于统一插桩:如所有 setImageBitmap() 加水印。
  • 实现方式
    • 自定义 Transform,用 ASMJavassist 遍历 class 文件并修改。
    • 常见框架:ByteX、booster、ASM 手写工具。

3. 基于 动态代理 / 反射(运行时 AOP)

  • 思路:利用 Java 动态代理CGLIB(在 Android 上不常用) 在运行时生成代理对象。

  • 限制

    • 只能代理 接口(因为 JDK 动态代理只能代理接口方法)。
    • 对 Android 性能有一定影响(尤其是频繁调用)。
  • 适用场景

    • JSBridge、接口统一拦截、埋点 SDK。
  • 示例

    public class ProxyHandler implements InvocationHandler {private final Object target;public ProxyHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Log.d("AOP", "调用前: " + method.getName());Object result = method.invoke(target, args);Log.d("AOP", "调用后: " + method.getName());return result;}
    }// 使用
    MyInterface proxy = (MyInterface) Proxy.newProxyInstance(MyInterface.class.getClassLoader(),new Class[]{MyInterface.class},new ProxyHandler(new MyInterfaceImpl())
    );
    

4. 基于 Hook / 插桩框架

  • 思路:通过系统 Hook 框架或 Xposed,对方法进行拦截。
  • 适用场景:逆向分析、黑科技应用,或者企业内的监控 SDK。
  • 常用框架
    • Xposed / EdXposed
    • Epic(美团的运行时 Hook 框架,支持 Android ART)
    • SandHook 等。

5. 对比与建议

方案时机优点缺点适合场景
AspectJ编译/打包期写法优雅,贴近 Spring AOP编译速度慢,Gradle 配置复杂日志埋点、性能监控
ASM/Javassist编译/打包期灵活,性能开销低学习成本高全局插桩、修改框架方法
动态代理运行时实现快,适合接口只能代理接口,性能差SDK 接口拦截、调试工具
Xposed/Epic运行时功能强大需 root/侵入性强第三方 Hook、逆向

写一个 Android 上 AspectJ AOP Demo,实现 拦截所有 View.OnClickListener 的点击事件,并做点击埋点(比如输出日志)。

这个 Demo 分三部分:

  1. Gradle 配置 AspectJ 插件
  2. 定义切面类 @Aspect
  3. 测试按钮点击是否被拦截

6. Gradle 配置

首先在 app/build.gradle 里加上 AspectJX 插件(常用的开源实现是 Hujiang AspectJX):

plugins {id 'com.android.application'id 'android-aspectjx' // 加上这一行
}android {namespace "com.example.aopdemo"compileSdk 34defaultConfig {applicationId "com.example.aopdemo"minSdk 24targetSdk 34versionCode 1versionName "1.0"}
}dependencies {implementation 'org.aspectj:aspectjrt:1.9.7'
}

根目录 build.gradle 里要加上插件 classpath:

buildscript {dependencies {classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'}
}

7. 定义切面类

app/src/main/java/com/example/aopdemo/aspect/ClickAspect.java 里写一个切面类:

package com.example.aopdemo.aspect;import android.util.Log;
import android.view.View;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;@Aspect
public class ClickAspect {private static final String TAG = "AOP_CLICK";/*** 定义切点:拦截所有 View.OnClickListener 的 onClick(View) 方法*/@Pointcut("execution(* android.view.View.OnClickListener.onClick(..))")public void onClickMethod() {}/*** 环绕通知:在点击前后都能插入逻辑*/@Around("onClickMethod()")public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {// 获取参数(点击的 View)Object[] args = joinPoint.getArgs();if (args != null && args.length > 0 && args[0] instanceof View) {View view = (View) args[0];int id = view.getId();String viewName = view.getResources().getResourceEntryName(id);Log.d(TAG, "点击事件埋点: viewId=" + id + " viewName=" + viewName);}// 执行原始方法(必须,不然点击逻辑会被拦截掉)joinPoint.proceed();}
}

8. Activity 测试

MainActivity.java 里随便放个按钮:

package com.example.aopdemo;import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button btn = findViewById(R.id.btn_test);btn.setOnClickListener(v -> Log.d(TAG, "按钮逻辑被执行"));}
}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:gravity="center"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/btn_test"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="点我试试"/>
</LinearLayout>

9. 运行效果

点击按钮时,Logcat 会输出两条日志:

D/AOP_CLICK: 点击事件埋点: viewId=2131230890 viewName=btn_test
D/MainActivity: 按钮逻辑被执行

✅ 这样就实现了 全局点击埋点 AOP
后续可以把 Log.d 换成上报到埋点 SDK、神策、Firebase 等。

10. 原理解析

AspectJ 里,ProceedingJoinPoint 是一个 连接点(JoinPoint)的运行时对象,它代表了当前被拦截的方法调用。


10. 1 它的来源

  • ProceedingJoinPoint 继承自 JoinPoint,专门用于 @Around 环绕通知。
  • JoinPoint 本身只能“看”,不能“改”;而 ProceedingJoinPoint 可以“执行原方法”,即调用 proceed()

10.2 它能拿到什么

@Around 方法里可以通过它获取很多信息:

方法说明示例
joinPoint.getArgs()获取目标方法的参数数组[View v]
joinPoint.getTarget()获取被代理对象(目标对象)某个 OnClickListener 实例
joinPoint.getThis()获取代理对象(AOP 生成的代理类)代理后的对象
joinPoint.getSignature()获取方法签名void onClick(View)
joinPoint.getKind()获取连接点类型method-execution
joinPoint.getSourceLocation()获取源码位置(类名、行号)MainActivity$1.onClick(MainActivity.java:27)

10.3 最重要的 proceed()

  • joinPoint.proceed():执行原始方法(带上原始参数)。
  • joinPoint.proceed(Object[] args):可以 修改参数后再执行

比如我们在点击埋点的时候,也可以偷偷改掉参数:

@Around("onClickMethod()")
public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {Object[] args = joinPoint.getArgs();if (args != null && args.length > 0 && args[0] instanceof View) {View view = (View) args[0];Log.d("AOP_CLICK", "点击: " + view.getId());}// 执行原方法(必须,不然点击逻辑不会继续)joinPoint.proceed(args);
}

10.4 在点击埋点场景里

  • joinPoint.getTarget() 👉 实际上就是 View.OnClickListener 对象
  • joinPoint.getArgs()[0] 👉 传入的 View v
  • joinPoint.proceed() 👉 真正调用 listener.onClick(v)

📌 总结一句:
ProceedingJoinPoint = 运行时对当前方法调用的描述 + 能控制是否继续执行原方法。


画一个调用流程图,展示 点击按钮 → AOP 切面 → proceed() → 原始 onClick 方法 的执行路径。


用户点击按钮
系统触发 View.OnClickListener.onClick()
进入 AOP 切面 @Around 通知
执行切面逻辑(埋点/日志/统计)
调用 joinPoint.proceed()?
拦截掉,不执行原始逻辑
执行原始 onClick 方法
按钮原有逻辑被执行
流程结束

🔍 解释

  1. 用户点按钮 → 系统调用 OnClickListener.onClick()
  2. 因为我们在方法上织入了 AOP → 先进入切面(@Around
  3. 切面逻辑(埋点、打印日志、防抖动判断…)
  4. joinPoint.proceed() 决定是否继续调用原始方法:
    • 调用了:进入原始 onClick 逻辑
    • 不调用:事件被“吃掉”,原始逻辑不会执行

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

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

相关文章

AI+预测3D新模型百十个定位预测+胆码预测+去和尾2025年8月21日第167弹

从今天开始&#xff0c;咱们还是暂时基于旧的模型进行预测&#xff0c;好了&#xff0c;废话不多说&#xff0c;按照老办法&#xff0c;重点8-9码定位&#xff0c;配合三胆下1或下2&#xff0c;杀1-2个和尾&#xff0c;再杀4-5个和值&#xff0c;可以做到100-300注左右。(1)定位…

机器学习【十】neural network

系统梳理了机器学习与神经网络的基础知识&#xff0c;涵盖理论、核心概念及代码实践。理论部分包括线性模型&#xff08;向量表示、广义线性模型&#xff09;、分类与回归的区别、梯度下降&#xff08;批量/随机/小批量&#xff09;、激活函数&#xff08;Sigmoid、ReLU等&…

如何用算力魔方4060安装PaddleOCR MCP 服务器

在当今数字化快速发展的时代&#xff0c;OCR&#xff08;光学字符识别&#xff09;技术已经成为从图像中提取文本信息的重要工具。无论是在自动化办公、智能文档处理还是在内容创作领域&#xff0c;OCR 技术的应用都极大地提高了工作效率和准确性。本文将详细介绍如何利用算力魔…

Azure的迁移专业服务是怎么提供的

好的&#xff0c;这是一个非常实际的问题。Azure的迁移专业服务&#xff08;Professional Services for Migration&#xff09;并非一个单一的“产品”&#xff0c;而是一个由微软及其庞大的合作伙伴生态系统共同提供的、基于成熟方法论的综合服务框架。其提供方式可以概括为&a…

Seaborn数据可视化实战:Seaborn入门-环境搭建与基础操作

Seaborn环境搭建与配置 学习目标 本课程将指导学员如何在不同的操作系统&#xff08;Windows, macOS, Linux&#xff09;上安装Seaborn库&#xff0c;以及如何配置Python环境&#xff0c;包括使用Jupyter Notebook和Spyder等集成开发环境&#xff08;IDE&#xff09;的基本操作…

Windows下RabbitMQ完整安装指南

一、RabbitMQ 简介 RabbitMQ 是一款基于 Erlang 语言开发的开源消息队列中间件&#xff0c;实现了高级消息队列协议&#xff08;AMQP&#xff09;。其最初起源于金融系统&#xff0c;专为分布式系统中的消息存储与转发设计&#xff0c;在可靠性、扩展性和高可用性方面表现卓越…

thingsboard 通过Entities hierarchy部件实现左边菜单点击,右边的表格按左边的分类型进行过滤筛选数据源

在 ThingsBoard 中&#xff0c;要让“Entities hierarchy”部件&#xff08;左侧树形导航&#xff09;与右侧的数据表格实现联动——即点击左侧某个节点后&#xff0c;右侧表格立刻按该节点对应的实体类型/层级进行过滤——需要把“数据源别名&#xff08;Alias&#xff09; 仪…

【Ansible】核心概念解析:架构、清单管理与配置入门

本专栏文章持续更新&#xff0c;新增内容使用蓝色表示。对于系统管理员而言&#xff0c;手动管理每一台服务器不仅维护难度极大&#xff0c;而且即使经验丰富&#xff0c;也难免出现疏忽和错误。自动化技术能有效避免因手动管理系统和基础架构而产生的各类问题。其优点包括&…

rs-fMRI_两篇文章中分析方法的梳理(近乎翻译)

文章一文章信息APOE ε4 influences within and between network functional connectivity in posterior cortical atrophy and logopenic progressive aphasia2024美国梅奥诊所发表在Alzheimers Dement. 的文章。“APOE ε4等位基因对后皮质萎缩与进行性语言障碍型失语症的网络…

在互联网大厂的Java面试:谢飞机的搞笑历险记

在互联网大厂的Java面试&#xff1a;谢飞机的搞笑历险记 在一个阳光明媚的早上&#xff0c;我们的主角&#xff0c;程序员谢飞机&#xff0c;走进了一家著名的互联网大厂&#xff0c;准备迎接他人生中最严峻的挑战——Java面试。 第一轮&#xff1a;基础技术面试 面试官&#x…

微软AD国产化替换倒计时——不是选择题,而是生存题

一直以来&#xff0c;微软Active Directory&#xff08;AD&#xff09;作为企业身份管理和访问控制的核心组件&#xff0c;承担着用户认证、权限分配、资源目录管理等基础职能。然而&#xff0c;随着政策、合规与网络安全压力不断加剧&#xff0c;AD面临着前所未有的挑战&#…

MyBatis-Plus MetaObjectHandler的几个坑(主要是id字段)

1.背景 主要是要实现一个id字段的自增长&#xff0c;不依赖数据库的能力&#xff08;已避免后续换库的问题&#xff09;。姑且使用redis作为表的id分配器&#xff0c;因此使用MyBatis-Plus MetaObjectHandler对每个insert的id进行分配。 2.实施过程 以下是实现过程 1.实现MetaO…

Springboot 项目配置多数据源

Springboot 项目配置多数据源 基础环境 java8、springboot2.2.13、mybatis、mysql5.x、oracle 项目配置 1.application.yml spring:datasource:mysql1:username: abcpassword: 123456url: jdbc:mysql://127.0.0.1:3306/panda?useUnicodetrue&characterEncodingUTF-8&z…

STM32_0001 KEILMDK V5.36 编译一个STM32F103C8T6说core_cm3.h文件找不到以及编译器版本不匹配的解决办法

KEILMDK V5.36 编译一个STM32F103C8T6说core_cm3.h文件找不到的解决办法利用KEILMDK V5.36 编译一个STM32F103C8T6说core_cm3.h文件找不到。主要错误信息如下D:/stm32studio/Armmdk/Packs/Keil/STM32F1xx_DFP/2.4.1/Device/Include\stm32f10x.h(486): error: core_cm3.h file n…

基于Transformer的机器翻译——训练篇

前言 还在为机器翻译模型从理论到落地卡壳&#xff1f;系列博客第三弹——模型训练篇强势登场&#xff0c;手把手带你走完Transformer中日翻译项目的最后关键一步&#xff01; 前两期我们搞定了数据预处理&#xff08;分词、词表构建全流程&#xff09;和模型搭建&#xff08…

智能编程中的智能体与 AI 应用:概念、架构与实践场景

一、智能体&#xff08;Intelligent Agent&#xff09;在编程中的定义与架构1. 智能体的核心概念 智能体是指在特定环境中能够自主感知、决策并执行动作的软件实体&#xff0c;具备以下特征&#xff1a;自主性&#xff1a;无需人工干预即可根据环境变化调整行为。交互性&#x…

数组实现各类数据结构

目录 一、数组实现单链表 二、数组实现双链表 三、数组实现栈 四、数组模拟队列 五、数组模拟单调栈 六、数组模拟单调队列&#xff08;滑动窗口&#xff09; 七、数组模拟堆 一、数组实现单链表 #include<iostream> #include<algorithm> #include<cstr…

数据处理与统计分析 —— apply自定义函数

目录 一、向量化与伪向量化 1、向量化 2、np.vectorize 伪向量化&#xff08;特定场景&#xff09; 3、apply&#xff08;自定义函数&#xff09; 二、apply函数 1、对series中使用apply 2、对dataframe中使用apply 3、apply函数案例-泰坦尼克号数据集] 数据集下载链接&#xf…

如何有效利用大语言模型来智能加速产业联盟的产业链转化路径?

观点作者&#xff1a;科易网AI技术转移研究院在科技创新浪潮席卷全球的今天&#xff0c;科技成果转化已成为衡量一个国家创新能力的重要标志。然而&#xff0c;一项权威调查显示&#xff0c;我国科技成果转化率不足30%&#xff0c;大量有价值的创新成果仍停留在实验室阶段&…

视频加水印 视频加水印软件 视频加动态水印

如果你有一个视频&#xff0c;你想给他加一个水印&#xff0c;那么你可以使用这个工具&#xff0c;准备好你的视频和水印。水印一般采用PNG&#xff0c;打开这个工具&#xff0c;把你的视频和水印拖进这个方框当中。视频限制是MP4&#xff0c;水印限制是PNG&#xff0c;它可以把…