JRebel for Android 的热部署方案还在吗

JRebel for Android 的热部署方案还在吗

JRebel for Android 的热部署方案还在吗


JRebel for Android 的热部署方案还在吗


JRebel 这个名字在 Android 开发圈子里已经沉寂了好几年。如果你是从 2015 年前后入行的开发者,可能还记得 ZeroTurnaround 公司那个绿色的火箭图标,以及它承诺的"修改代码后秒级看到效果"的魔法。当时 Android Studio 的 Instant Run 还处在被疯狂吐槽的阶段,编译一次动辄几十秒,JRebel for Android 确实解决了一个真实的痛点。但 2017 年底 ZeroTurnaround 宣布停止维护 JRebel for Android,这个工具就慢慢从讨论中消失了。现在已经是 2024 年,Android Studio 的 Apply Changes、Compose Preview、以及各种 Gradle 优化层层叠加,我们还需要关心热部署吗?或者说,JRebel 的遗产有没有以某种形式延续下来?


这个问题我最近重新想起来,是因为一个挺具体的场景。我们在维护一个大型模块化项目,App 工程里拆出了三十多个 Gradle module,每次修改一个底层 common 模块里的工具类,全量编译要跑四分多钟。Apply Changes 在这种情况下直接失效——它要求修改的代码必须在当前部署的 APK 里,而且不能触及某些签名相关的类。Instant Run 早就被 Google 自己干掉了,Apply Changes 的覆盖率又有限。这时候我开始重新搜寻,看看有没有现代替代方案,以及 JRebel 本身是不是彻底死了。


JRebel 的历史遗产:它到底做了什么


先回顾一下 JRebel for Android 的技术原理,这有助于理解为什么它后来死了,以及现在的替代方案为什么长成这样。


JRebel 的核心机制是字节码替换,不是重新编译打包。它在构建流程里插入了一个 agent,把 APK 里的 .dex 文件做 instrument,注入一些钩子。运行时,当你修改了 Java/Kotlin 代码,JRebel 的 IDE 插件会对比变更,生成一个增量 patch,通过 ADB 推送到设备上。这个 patch 被设备的 agent 拦截,用新的字节码替换掉旧的,同时尽量保持对象状态。对于 Android 来说,这意味着 Activity 不用重建、Application 不用重启,甚至某些静态变量都能保留。


这和 Android Studio 的 Instant Run 有本质区别。Instant Run 是 Google 官方方案,它用了类似的思路(也是运行时替换),但实现上更侵入。它把 App 拆分成多个 dex,通过代理 Application 来加载,导致大量兼容性问题。很多开发者都遇到过 Instant Run 开启后,某些反射调用、ClassLoader 相关的逻辑直接崩溃。Google 最终在 Android Studio 3.5 里彻底移除了 Instant Run,改用 Apply Changes。


Apply Changes 走的是另一条路。它依赖 Android 8.0(API 26)引入的 Runtime Resource Overlay 和 JVMTI 接口。JVMTI 是 Java 虚拟机工具接口,允许调试器在运行时重新定义类。Apply Changes 的 "Apply Code Changes" 就是利用 JVMTI 的 RedefineClasses 功能,直接替换方法体。但 JVMTI 的 RedefineClasses 有严格限制:不能添加、删除方法或字段,不能修改继承关系,不能修改注解。这就是我为什么遇到 common 模块里的工具类修改后 Apply Changes 直接灰掉——那个类被很多模块依赖,方法签名一变,JVMTI 就无能为力了。


JRebel 当年没有这些限制,因为它自己实现了更底层的替换机制,不完全依赖 JVMTI。代价就是兼容性维护是个无底洞。Android 每个大版本都有 ART 虚拟机的改动,ZeroTurnaround 需要持续跟进,而 Android 开发者的付费意愿又不足以支撑这个成本。2017 年的停更公告里,他们明确说"Android 平台的碎片化使得维护成本过高"。


现在的官方方案:Apply Changes 的边界


Android Studio 现在的 Apply Changes 分成两个按钮:"Apply Code Changes" 和 "Apply Changes and Restart Activity"。前者用 JVMTI,后者用 APP 重启但保留 Application 状态。Google 官方文档里有一张很诚实的表格,列出了什么情况下哪个按钮能用。


实际使用中,Apply Code Changes 的覆盖率大概能覆盖日常 UI 调试的 60% 左右。改个 Compose 里的颜色值、调整一下 RecyclerView 的 item 布局,通常没问题。但一旦触及到数据类字段变更、新增方法、修改 lambda 表达式(Kotlin 的 lambda 会生成额外的合成方法),它就罢工了。更隐蔽的问题是,某些代码变更虽然 JVMTI 允许替换,但运行时状态不一致会导致崩溃。比如你把一个方法的返回值从 String 改成 Int,已经存在的调用方对象可能还在按旧逻辑执行,这时候 ClassCastException 很难避免。


Apply Changes and Restart Activity 这个选项相对可靠,但它需要 Activity 重建。如果你的 Activity 里有大量初始化逻辑,或者用了单例模式管理状态,重建后的状态和之前不一致,调试体验也很割裂。而且它对 Application 级别的变更完全无效——改了自定义 Application 类里的代码,只能全量重装。


Google 在 2023 年的 Android Dev Summit 上提到,他们正在把更多场景纳入 Apply Changes 的支持范围,但核心限制来自 JVMTI 本身,这不是 Android Studio 团队能单方面突破的。所以官方方案的天花板是明确的。


第三方替代方案:现在的选择有哪些


JRebel 死后,这个领域并没有完全空白。我梳理了一下目前还能找到的几个方向,每个都有明确的适用场景和硬伤。


Freeline


阿里在 2016 年开源的 Freeline 曾经是个热门替代方案。它的思路和 JRebel 类似,也是增量编译 + 运行时替换,但专门针对 Android 做了大量优化。Freeline 的核心是绕过 Gradle,用自定义的编译流程只编译变更的模块,然后通过 ADB 推送增量 dex。它甚至支持资源文件的增量更新,用 aapt 的增量模式处理。


Freeline 在 2017 年左右确实解决了很多团队的编译速度问题。但它的维护周期很短,阿里内部后来转向其他方案,开源版本在 2018 年后基本停滞。现在 GitHub 上的 alibaba/freeline 仓库最后一次提交是 2018 年 12 月,累计 3.8k star,但 issue 区大量未回复的问题。Android Gradle Plugin 3.0 以后引入的 API 变更、AndroidX 迁移、R8 代码压缩,Freeline 都没有跟进。现在如果要接 Freeline,大概率需要 fork 后自己维护,这个成本对于大多数团队来说不划算。


Buck / Bazel 的增量构建


Facebook 的 Buck 和 Google 的 Bazel 都强调增量构建和缓存复用。Buck 的 exopackage 机制可以实现类似热部署的效果,把 App 拆成多个小包,只推送变更的部分。Buck 本身在 Facebook 内部用了很久,但开源社区的接受度不高,2018 年后 Facebook 自己也慢慢转向其他方案。Bazel 的 Android 规则集(rules_android)一直在维护,增量构建能力确实强,但 Bazel 的学习曲线和迁移成本是出了名的高。一个成熟的 Gradle 项目迁移到 Bazel,通常需要专门的基础设施团队投入几个月时间。


Bazel 的增量构建解决的是"编译快"的问题,不是"运行时替换"的问题。它编译完还是需要安装 APK,只是编译时间从分钟级降到秒级。对于大型项目这很有价值,但它不解决 JRebel 当年承诺的"不重启看到效果"这个场景。


Jetpack Compose 的 Preview


Compose 的 @Preview 注解是个很有意思的替代路径。它不是在真机上热部署,而是在 IDE 里渲染 Compose 组件。Android Studio 的 Compose Preview 现在支持交互模式、动画检查、甚至数据预览。对于纯 UI 调试来说,这个体验比当年的 JRebel 还要好——不需要真机、不需要编译、修改代码后预览面板实时刷新。


但 Preview 的局限也很清楚。它只支持 Compose 组件,不支持传统 View 体系。而且 Preview 运行在 Desktop JVM 上,不是 Android Runtime,某些 Android 特有的 API(比如 SensorManager、Camera 相关的调用)无法直接预览。更关键的是,Preview 不运行你的完整 App 逻辑,ViewModel、Repository、网络层这些都绕过了。所以它是个 UI 层的快速迭代工具,不是全量热部署方案。


JRebel 本体:Java 后端还活着


一个容易混淆的点是,JRebel for Android 死了,但 JRebel 本身(针对 Java 后端开发的版本)还活着,而且活得不错。ZeroTurnaround 公司在 2017 年被 Perforce 收购,JRebel 的 Java 版本现在是 Perforce 产品线的一部分,官网 jrebel.com 上仍然提供下载和订阅。个人开发者年费大约 500 美元,企业版按席位收费。


JRebel 的 Java 版本支持 Spring Boot、Micronaut、Quarkus 等主流框架,机制仍然是运行时字节码替换。但 Android 的 ART 虚拟机和标准 HotSpot JVM 差异太大,JRebel 的 Android 分支没有复活迹象。我专门发邮件问过 Perforce 的售前支持,回复很明确:"JRebel for Android 已经停止维护,目前没有重启计划。"


一个还活着的选项:Apply Changes 的增强方案


在搜寻过程中,我发现了一个相对冷门但确实可用的工具:Apply Changes 的第三方增强实现,以及基于 JVMTI 的自定义方案


有一个叫 R8 Apply Changes 的开源实验项目(GitHub 上能找到几个相关仓库,但活跃度不高),尝试在 Apply Changes 的基础上扩展支持范围。核心思路是用 R8 的代码改写能力,把某些"不兼容"的变更转换成"兼容"的形式。比如把新增字段改成通过 Map 外挂存储,把方法签名变更改成内部转发。这个方案的问题在于稳定性和性能——额外的代码转换层引入的 bug 很难调试,而且运行时开销明显增加。


更务实的方向是基于 JVMTI 自己实现部分热部署。Android 的 Debug 类(android.os.Debug)在 API 28 以上提供了 attachJvmtiAgent 方法,允许在运行时加载自定义 JVMTI agent。这意味着你可以自己写一个 native agent,在运行时做更激进的类替换。但 JVMTI 的 RedefineClasses 限制是硬编码在 ART 里的,agent 也不能突破"不能增删方法"这个边界。能做的主要是监控、日志增强、轻量级的 AOP,不是真正的热部署。


我实际试过一个基于 JVMTI 的实验项目:在 GitHub 上搜索 "android jvmti hot swap" 能找到几个原型,但都是个人实验性质,没有生产可用的。其中一个比较完整的是 2019 年 Google 官方示例 android-jvmti-agent,演示了如何写 agent 和加载,但明确标注"仅用于学习和调试"。


另一个角度:编译速度本身的优化


既然运行时替换的天花板很难突破,很多团队把精力放在"让编译快到不需要热部署"这个方向上。


Gradle 的 Build Cache 和 Configuration Cache 是官方主推的优化。Configuration Cache 在 Gradle 7.0 以后稳定,能跳过配置阶段的重复执行。对于大型项目,配置阶段可能占到总时间的 30% 以上,这个优化很实在。但 Configuration Cache 对插件的兼容性要求严格,很多老项目的自定义 Gradle 插件需要改造。


Remote Build Cache 是更激进的方案。Gradle Enterprise(现在叫 Develocity)提供远程缓存服务,团队成员可以复用彼此的编译产物。Gradle 官方文档里有配置说明,企业版需要付费,但核心功能在个人和小团队也有免费额度。实际使用中,远程缓存的命中率取决于代码变更的局部性。如果每个人都在改同一个模块,缓存效果会打折扣。


还有一个不太被提及但效果显著的优化:模块边界重构。很多项目的编译慢,根本原因是模块依赖关系混乱。一个底层模块的修改触发大量上层重新编译,通常是因为上层模块直接依赖了底层模块的内部实现细节。用 Gradle 的 api/implementation 依赖配置做隔离,配合 Java 的 package-private 访问控制,能显著减少变更传播范围。这个工作不 flashy,但投入产出比很高。我们项目里花两周做了一次依赖梳理,全量编译时间从 4 分多钟降到 2 分钟以内,增量编译的常见场景进入 30 秒区间。


我现在的实际做法


回到我开头说的那个场景:三十多个模块的大型项目,修改 common 工具类后的编译痛苦。


我的实际做法是分层的,没有银弹,但组合起来能用。


对于纯 Compose UI 的调试,优先用 Preview。Android Studio Hedgehog(2023.1.1)版本的 Preview 已经支持了 @Preview 的参数化预览和交互模式,大部分组件级调试不需要上真机。Preview 的刷新延迟在秒级,比任何热部署方案都快。


对于需要真机验证的逻辑,先用 Apply Changes 探探路。如果它灰掉了,看变更的性质。如果只是方法体内部的实现调整,JVMTI 通常能处理。如果触及类结构,改用 Apply Changes and Restart Activity,同时确保 Activity 的重建逻辑是干净的——这其实是倒逼代码质量的好事,Activity 不应该在 onCreate 里做太重的事。


对于 Application 级别或者必须全量重装的场景,我配置了一套 Gradle 任务链,用 Build Cache + 并行编译把重装时间压到能接受的范围。具体数字:我们项目现在的 clean build 大约 2 分 10 秒,增量 build 平均 25 秒,最坏的 common 模块变更大约 1 分 40 秒。这个时间里包含了自动推送到测试设备并启动。作为对比,2016 年用 JRebel 的时代,虽然热部署本身秒级,但初次编译的基线时间也不短,而且 JRebel 的 instrument 过程会增加构建复杂度。


还有一个不太符合直觉的发现:真机本身的性能对调试体验影响很大。我们测试设备从 Pixel 3 换到 Pixel 7,ADB 推送和安装的速度提升非常明显。Google 在 Android 11 引入的 ADB Incremental 安装(基于增量 FS)对大型 APK 很友好,需要设备端支持。Pixel 7 上我们的 80MB APK 增量安装能进到 10 秒以内,这压缩了"必须重装"场景的痛苦。


关于 JRebel 的遗产:它教会了我们什么


JRebel for Android 的死亡,本质上是一个商业模式和技术现实的错配。ZeroTurnaround 的收费模式(个人订阅制)在 Java 后端市场能跑通,因为后端开发者的付费意愿强、JVM 的稳定性高。Android 开发者群体对价格敏感,而且 Android 系统的碎片化把维护成本拉到了一个无法覆盖的高度。


但它留下的技术思路并没有完全消失。JVMTI 的引入、Apply Changes 的设计、甚至 Compose Preview 的实时刷新,都可以看作是这个方向的遗产。Google 作为平台方,有能力把热部署做进系统层和工具链,虽然能力范围比 JRebel 窄,但稳定性和兼容性更好。


我个人觉得,Android 开发现在处于一个"没有完美的热部署,但组合方案够用"的状态。和 2015 年相比,编译速度本身的优化空间被挖掘得更深,热部署的刚需程度其实下降了。当年 JRebel 解决的是"编译 5 分钟、调试 10 秒钟"的畸形比例,现在 Gradle 优化后这个比例已经改善很多。


如果你现在还在寻找 JRebel 的直接替代,结论可能让人失望:没有功能对等、稳定维护、开箱即用的第三方方案。官方工具链 + 编译优化 + 架构层面的模块隔离,是更务实的路线。对于特定场景(纯 Compose UI),Preview 的体验甚至超越了当年的 JRebel。


最后提一个边缘选项:如果你在做 Android 库开发而不是 App 开发,Java 版的 JRebel 理论上可以用于单元测试和 JVM 端的调试。Android Library 模块的纯 Kotlin/Java 逻辑可以在 JVM 测试里验证,这时候用 JRebel 加速测试迭代是可行的。但这条路径很绕,需要把业务逻辑和 Android 框架依赖拆得很干净,不是通用方案。


JRebel 的官网现在还挂着,Java 版本的下载链接有效,但 Android 那个绿色火箭已经不会再升空了。这个工具的历史位置,更像是 Android 开发工具链成熟过程中的一个过渡角色——它证明了热部署的需求真实存在,也证明了在 Android 这个特定生态里,第三方商业工具很难长期维持。现在的解决方案分散在官方工具链的各个层面,没有统一的"热部署"品牌,但体验在缓慢而确实地进步。

AI 助手的代码审查能力,能替代 Code Review 吗 2026-06-17
Compose 的副作用 API:LaunchedEffect、DisposableEffect、SideEffect 到底什么时候用 2026-06-18

评论区