【免费下载链接】S-MVP
🔥🔥优化版MVP,使用注解泛型简化代码编写,使用模块化协议方便维护,APT过程使用注解解析器利用JavaPoet🌝完成重复模块的编写,利用ASpect+GradlePlugin 完成横向AOP编程+Javassist动态字节码注入+Tinker实现热修复+Retrofit实现优雅网络操作+RxJava轻松玩转数据处理
🔥 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 | 织入切面逻辑的 class | myplugin/ 的AspectjPlugin |
| 3️⃣ Javassist 注入 | Gradle Transform + javassist | class 文件 | 注入 OkBus 注册/注销/分发代码的 class | myplugin/ 的JavassistTransform |
| 4️⃣ dex 打包 | AGP(aapt/d8) | 全部 class | dex → APK | AGP 默认流程 |
下面按构建顺序逐个拆解。
二、阶段一: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()。
💡 两个细节值得注意:
- 生成的代码里尽量不要再加注解,否则会触发下一轮注解处理,形成递归(源码注释中有说明);
@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 做三件事:
- 在
onCreate(或自动生成)里插入OkBus.getInstance().register(...)注册代码; - 在
onDestroy里插入unRegister(...)注销代码; - 动态生成一个
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的增强链路是这样的:
- javac 前:
libcomplier的 APT 处理器扫描注解,用 JavaPoet 生成$$ViewBinder、InstanceFactory等新 java 文件,加入本轮编译; - javac 后:AspectjPlugin 的
doLast触发 ajc,把 4 个切面编织进全部 class; - Transform 阶段:JavassistTransform 扫描 class,为
@Bus类注入 OkBus 注册/注销/分发代码; - 打包阶段: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轻松玩转数据处理
相关推荐
1 条命令解决百万行翻找难题:用 xsv search 的 CSV 正则搜索,把 2GB 运单干成 3 秒查询
1 条命令解决百万行翻找难题:用 xsv search 的 CSV 正则搜索,把 2GB 运单干成 3 秒查询 凌晨两点,客服弹过来一条消息:昨天导出的运单里,
数据分析CLI数据工程揭秘Atmosphere固件打包:从Package2到Package3的构建全流程
揭秘Atmosphere固件打包:从Package2到Package3的构建全流程 Atmosphere作为Nintendo Switch的定制固件,其打包流程
固件嵌入式操作系统系统编程xtool核心揭秘:Darwin SDK 构建原理——如何从Xcode.xip打造iOS交叉编译环境
xtool核心揭秘:Darwin SDK 构建原理——如何从Xcode.xip打造iOS交叉编译环境 xtool 是一款跨平台 Xcode 替代品,让开发者在
开发工具CLI移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考