ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

S-MVP构建全流程揭秘:APT生成→AspectJ编织→Javassist注入→dex打包如何环环相扣

S-MVP构建全流程揭秘:APT生成→AspectJ编织→Javassist注入→dex打包如何环环相扣

【免费下载链接】S-MVP

🔥🔥优化版MVP,使用注解泛型简化代码编写,使用模块化协议方便维护,APT过程使用注解解析器利用JavaPoet🌝完成重复模块的编写,利用ASpect+GradlePlugin 完成横向AOP编程+Javassist动态字节码注入+Tinker实现热修复+Retrofit实现优雅网络操作+RxJava轻松玩转数据处理

项目地址:https://gitcode.com/gh_mirrors/smv/S-MVP
点击查看免费下载

🔥 S-MVP 是一个优化版 Android MVP 框架,用注解+泛型简化代码编写。它的最大亮点藏在编译期:一次 Gradle 构建会串联起APT 注解生成代码 → AspectJ 织入切面 → Javassist 字节码注入 → dex 打包四个环节,让你少写大量样板代码。本文将带你快速看懂这条构建流水线如何环环相扣。

一、先看全景:四大阶段各管一段

在 README.md 中,作者把处理流程总结为一句话:

在 build 过程我们可以通过 APT 生成 java 文件,再通过 AspectJ 解析、编织成 class,最后通过 Javassist 修改 class 和 jar 文件,最终打包成 dex 到 apk。

用一张表理清分工:

阶段技术处理对象产出代码位置
1️⃣ APT 生成AnnotationProcessor + JavaPoet注解元数据(源码层)新增.java文件libcomplier/
2️⃣ AspectJ 编织aspectjtools(ajc)已编译的 class织入切面逻辑的 classmyplugin/ 的AspectjPlugin
3️⃣ Javassist 注入Gradle Transform + javassistclass 文件注入 OkBus 注册/注销/分发代码的 classmyplugin/ 的JavassistTransform
4️⃣ dex 打包AGP(aapt/d8)全部 classdex → APKAGP 默认流程

下面按构建顺序逐个拆解。

二、阶段一:APT 注解处理器在编译期"写代码"

2.1 入口:一个 Processor 分发三个子处理器

AnnotationProcessor.java 通过@AutoService(Processor.class)自动注册,它监听了@InstanceFactory、@$(BindView)、@OnResume等注解,然后在process()中依次调用:

  • InstanceProcessor—— 生成 Presenter 实例工厂
  • BindViewProcessor—— 生成视图绑定类
  • LifeProcessor—— 处理生命周期注解

📌 生成的 Java 文件会落在app module/build/generated/source/apt目录下,随后被正常编译。

2.2 生成视图绑定类$$ViewBinder

这是最直观的收益。你在字段上写一个注解,构建期 BindViewProcessor.java 就会用 JavaPoet 生成类似MainActivity$$ViewBinder的类,内部就是一连串findViewById的等价代码。运行时由 InjectView.java 通过反射Class.forName(className + "$$ViewBinder")找到并执行绑定,于是 BaseAppCompatActivity 只需一行InjectView.bind(this)就完成了所有控件注入——注解定义见 $.java。

2.3 生成 Presenter 实例工厂

InstanceProcessor.java 则扫描所有标注@InstanceFactory的类(即各个 Presenter),生成一个带switch的静态工厂方法,配合 InstanceFactory.java 注解,让 Presenter 的创建"隐形"——Activity 里不需要手写new XxxPresenter()。

💡 两个细节值得注意:

  1. 生成的代码里尽量不要再加注解,否则会触发下一轮注解处理,形成递归(源码注释中有说明);
  2. @InstanceFactory生成的create()方法本身还带了@MemoryCache注解——也就是说 APT 的产物会被下一个阶段 AspectJ 继续"加工",这正是环环相扣的起点。

三、阶段二:AspectJ 在 class 文件上"编织"切面

3.1 插件怎么介入构建

AspectjPlugin.groovy 挂在javaCompile.doLast钩子上:等 javac 把所有 class 编译出来后,调用 AspectJ 编译器org.aspectj.tools.ajc.Main,参数核心是:

  • -inpath/-d指向 class 输出目录,原地编织;
  • -aspectpath传入完整 classpath,让编译器能发现切面类。

app 模块最后一行apply plugin: AspectjPlugin(见 app/build.gradle)把它激活,插件本体发布到 repo/com/app/plugin/ 本地仓库供根构建文件引入。

3.2 四个自带切面

项目内置了四个切面,全部放在 app/src/main/java/com/wangpos/s_mvp/aop/ 目录:

切面触发注解作用
TimeLogAspect@TimeLog自动打印方法耗时,开关由BuildConfig.isTime控制
CheckLoginAspect@CheckLogin方法执行前校验登录状态
MemoryCacheAspect@MemoryCache方法结果写入 Lru 缓存
TryCatchAspect@TryCatch统一捕获异常,防止崩溃

以 TimeLogAspect.java 为例:它声明@Pointcut("execution(@com.example.aspectj.TimeLog * *(..))"),凡是被@TimeLog标记的方法,编织后会被around环绕——先记时间、执行原方法、再打印耗时。业务代码对此完全无感知。更多概念说明可参考 sources/aop.md。

🎯 这一步的意义:切面逻辑被直接写进 class 字节码,运行时零反射、零代理开销。

四、阶段三:Javassist 用 Transform 改写 class 字节码

4.1 Transform 拦截 class 流

JavassistTransform.groovy 实现了 AGP 的Transform接口:getInputTypes()声明只接收CLASSES类型输入,作用范围覆盖项目代码、子模块与外部库(getScopes())。在transform()中,它对每个 jar 和目录输入调用MyInject.injectDir(...),再把处理后的内容写入 output 目录,交给后续 dex 步骤。

4.2 到底注入了什么:OkBus 事件总线

MyInject.groovy 遍历com包下的每个 class,用 javassist 的CtClass读取方法上的注解:@Bus(事件方法)、@BusRegister、@BusUnRegister。随后 BusHelper.groovy 做三件事:

  1. 在onCreate(或自动生成)里插入OkBus.getInstance().register(...)注册代码;
  2. 在onDestroy里插入unRegister(...)注销代码;
  3. 动态生成一个call(Message msg)方法,内含switch(msg.what)事件分发逻辑,并把类实现Event接口。

也就是说,你在方法上标一个@Bus(事件ID),构建期就自动获得了"注册 + 注销 + 分发"全套代码,而源码一行没动。这正是 README 所说的"通过 javassist 修改字节码,在不修改源码的情况下完成 AOP,避免代码碎片化"。

⚠️ 常见坑:CtClass用完后必须c.detach(),否则 ClassPool 里残留旧代码,增量构建会出怪问题(源码中已明确注释)。

五、阶段四:dex 打包,一切归入 APK

经过 Transform 处理后的 class 文件沿 AGP 标准流水线向下流动:

class(被 Javassist 改写)→ dx/d8 转换 → .dex → aapt 与资源/manifest 合并 → 签名 → APK

对开发者来说这一步完全透明:前三阶段的产物都已经是"最终代码",dex 只是格式转换。也正因为所有增强都发生在构建期,运行时不需要额外引擎支撑。

项目描述中提到的 Tinker 热修复则是这条链路的延伸:由于业务代码集中在清晰的 MVP 分层(见上方架构图),补丁可以按模块粒度替换 dex 中的目标类,配合 app/src/main/java/com/wangpos/s_mvp/ 下按功能分包的组织方式(ui/login、ui/meizi、ui/wxnews等),补丁范围更容易控制。

六、一次构建的时间线速览

把四个阶段串起来,一次assembleDebug的增强链路是这样的:

  1. javac 前:libcomplier的 APT 处理器扫描注解,用 JavaPoet 生成$$ViewBinder、InstanceFactory等新 java 文件,加入本轮编译;
  2. javac 后:AspectjPlugin 的doLast触发 ajc,把 4 个切面编织进全部 class;
  3. Transform 阶段:JavassistTransform 扫描 class,为@Bus类注入 OkBus 注册/注销/分发代码;
  4. 打包阶段:AGP 将增强后的 class 转 dex、合资源、签名,产出 APK。

每一步都以上一步的输出为输入,环环相扣——源码保持干净,增强逻辑全部沉淀在构建体系里。

七、上手建议:如何快速验证

  • 🛠 构建后查看app/build/generated/source/apt/,能看到 APT 生成的真实 Java 源码;
  • 🛠 观察 Build 输出日志:AspectjPlugin会打印"Aspectj切片开始编织Class",JavassistTransform会打印耗时(JavassistTransform cast : xx secs);
  • 🛠 给任意方法加上@TimeLog重新构建,运行后即可在 Logcat 的timeTAG 下看到耗时打印,亲测整个链路生效;
  • 🛠 想理解切面原理,优先阅读 sources/aop.md 与 TimeLogAspect.java;
  • 🛠 想理解任务调度等运行时能力,可参考 SmartTaskManager 结构图:

八、总结

S-MVP 的构建流水线是"编译期魔法"的教科书式组合:

  • APT + JavaPoet负责"从无到有"生成代码,消灭findViewById与 Presenter 创建样板;
  • AspectJ负责"切面编织",耗时日志、登录校验、缓存、异常捕获以注解形式一行接入;
  • Javassist Transform负责"字节码级注入",事件总线的注册/注销/分发全自动完成;
  • dex 打包将所有成果固化进 APK,运行时零负担。

理解这条 APT→AspectJ→Javassist→dex 的链路,你也能在自己的项目中挑出最需要的环节复用——这正是编译期增强思想的精髓。✨

【免费下载链接】S-MVP

🔥🔥优化版MVP,使用注解泛型简化代码编写,使用模块化协议方便维护,APT过程使用注解解析器利用JavaPoet🌝完成重复模块的编写,利用ASpect+GradlePlugin 完成横向AOP编程+Javassist动态字节码注入+Tinker实现热修复+Retrofit实现优雅网络操作+RxJava轻松玩转数据处理

项目地址:https://gitcode.com/gh_mirrors/smv/S-MVP
点击查看免费下载

相关推荐

上一篇:用原子距离约束引导折叠:BioEmu-2接触条件(Contact Conditioning)特性实操技巧
下一篇:ThinkingCap-Qwen3.6-27B-OptiQ-4bit KV Cache 混合量化指南:长上下文下的内存与速度优化

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进