2026年了,我坐在工位上盯着模拟器,改了某个 Composable 的Modifier.padding,界面上居然原地刷新了,连鼠标都没动。这一幕我等了很久。Android Compose 的 Hot Reload 终于来了,官方正式把运行时热重载补上了,可随之而来的还有一张订阅账单。先说结论:功能本身是真香,方案给得也很大方,但“官方功能开始收费”这件事,让不少开发者破了防。
这篇文章我想完整记录两件事:第一,这个 Hot Reload 到底是怎么做到的、用起来什么感受;第二,收费之后我们到底该怎么选。我会把配置过程、实测数据、踩坑细节、费用测算都摊开写,给还没决定买不买的人一个参考。
1. 等了这么多年,Hot Reload 卡在了哪里
1.1 不是「编译慢」一个原因
很多朋友以为 Compose 的热重载难做,是因为 Gradle 编译慢。其实编译只是表象,核心难点在于 Compose 的运行时模型。
Composable 本质上是一堆被 Kotlin 编译器改写过的函数调用,每次重组都会执行这些函数体。UI 的更新依赖一个基于状态快照的调度系统,外部输入变了,重组范围会自动缩小到受影响的那一段。听起来很适合热重载,对吧?但问题恰恰出在这套“自动缩小”机制上。
热重载要做的不是简单替换字节码,而是要把新的函数体塞进一个已经运行中的进程,同时还要让这个进程里保存的各种remember状态、协程作用域、导航栈保持原样。旧函数可能被旧状态引用,新函数又可能有不同的局部变量结构,编译器不能在运行时轻易地“拆掉重组树再重建”。
以前官方不是没试过,推出过实验性的 Live Edit,能改一部分布局参数并在预览窗口里即时看到效果。但那种方案只覆盖预览场景,改的是构造参数,真正跑在模拟器上的业务逻辑和 UI 状态一碰就崩,所以一直处于“实验”阶段。我自己在项目里试过几次,最后都因为“改两行字要等 30 秒全量编译”而退回来了。
1.2 View 时代的老办法为什么不算数
传统 View 系统下,开发者对“热更新”的概念来自另一个地方:代码改完直接跑起来,Activity 重建,系统帮你重新走一遍onCreate。哪怕是更早的即时运行方案,也逃不开重启 Activity 或者重启进程。
这套逻辑本质上是“状态可以丢,界面可以重来”。Activity 重建之后,onSaveInstanceState能救回来一部分,剩下的本来就不保证。所以 View 时代大家能接受,反正 App 重启一次也就几秒钟。
但 Compose 的设计哲学不一样。它把状态提升、状态保存在remember和ViewModel里,UI 只是状态的一个投影。热重载如果动不动就重建整个 Activity,那就等于把状态丢了个精光,Compose 的优势全没了。用户会问:我填到一半的表单,改个按钮颜色就给我清了,这算什么“热重载”?
所以官方迟迟不发布,不是因为懒,是因为他们想做的其实是“热更新 + 状态保留”两个目标同时达成,这在技术难度上直接跳了一级。
1.3 这次官方终于换了一条路
2026 年这次正式方案能够落地,我理解的大方向是把编译流程拆成了两段:符号索引阶段和增量类加载阶段。
开发模式下,编译器会额外生成一份模块级别的索引文件,标记出哪些 Composable 函数体、哪些Modifier扩展、哪些资源字段发生过变化。运行时通过一个开发代理进程监听这些变化,把变更后的字节码动态加载进目标进程,再针对性地触发一次重组调度。
这里的关键是:它不动 Activity,不重启进程,也不清空remember状态。理论上,只要你的函数签名不变、状态来源不变,改 UI 层代码就像换一套皮肤一样。这也是为什么它的体验终于接近前端 Hot Reload 的感觉——改完即所见,界面站在原地被更新。
了解这个原理之后,我也就理解了一件事:为什么实现起来要付出那么大的编译和运行时代价,也理解了为什么官方最后会选择收费——它确实不是顺手加个开关那么简单。
2. Hot Reload 上手实测:哪些能热,哪些不能热
2.1 启用它需要动哪些配置
先说启用过程,不算复杂,但有几个前置条件容易被忽略。
我这个项目用的是常规的多模块结构,app壳工程依赖feature:home、feature:profile这些业务模块。启用 Hot Reload,需要把项目先升级到 2026 年发布的工具链版本:Gradle 插件版本、Compose 编译器版本、Compose 运行时版本,三者的匹配关系要一致。
然后是模块级别的开关,在每个需要支持热重载的模块的构建配置里,开启开发模式构建:
android { composeOptions { hotReload.setEnabled(true) hotReload.indexMode.set(IndexMode.INCREMENTAL) } }IndexMode我一开始没管,默认是全量索引,首次连接会比较慢。改成增量索引之后,首次构建大约多花 20 秒生成索引文件,但后续每次变更只会扫描差异部分,快很多。
连接方式走的是开发环境自带的设备连接通道,跟调试连接共用一条链路。也就是说,你平时能run到哪台设备,Hot Reload 就能连到哪台设备,不需要额外开端口或者装什么独立代理,这一点省了不少事。
2.2 支持范围与不支持范围的边界
我把实测下来支持和不支持的情况整理成一个表,这个表比官方文档上的描述更接近真实开发场景:
| 支持的情况 | 不支持的情况 |
|---|---|
| Composable 函数体内部改动(布局、修饰符、分支逻辑) | 函数签名变化(参数类型、返回值类型) |
| 主题颜色、字体、动态资源引用调整 | remember的 key 结构变化 |
| 字符串资源、尺寸资源的增量替换 | 新增顶层类或顶层函数(需要完整编译) |
| 数据库实体类之外的数据类字段变更 | 协程作用域、ViewModel实例创建逻辑变更 |
| 枚举常量与不可变列表的替换 | 涉及依赖注入图更新的改动 |
实测里最有价值的是“Composable 函数体改动”这项。我改页面标题、按钮间距、背景颜色,甚至调整一个LazyColumn内部的 item 布局,都能在 1 到 3 秒内看到模拟器上的真实效果,不需要重新进页面,也不清空当前屏幕。
最有价值的一点是:改主题色系统的中间态,比如把MaterialTheme里的颜色值改掉,全局 UI 会瞬间变,因为主题对象在状态树里的位置没有变,只是属性换了。
要特别小心“新增顶层类”这类变更。我最初以为只要能改函数体就能加新函数,结果现场打脸,一旦在文件里新增了一个顶层@Composable函数,Hot Reload 就直接提示“变更超出可热更新范围,需要完整编译”。这种时候只能老老实实等全量构建,没有捷径。
2.3 真实场景下的限速与体验
我拿一个中等模块做了实测对比。这个模块编译一次全量 debug 构建大约 40 秒,如果有大量 Kotlin 编译任务,甚至要 1 分钟以上。
用上 Hot Reload 之后:
- 第一次连接建立:约 8 秒,包含索引生成和代理连接握手。
- 单文件单函数小幅改动(改 padding):约 1.2 秒看到界面更新。
- 单文件多个函数改动(同时改两个 Composable):约 2.5 秒。
- 跨模块改动(改
feature:home又改core:designsystem):约 4 秒,需要同步两个模块的索引。
这个速度带来的开发体验提升很直观:以前等着看一个 UI 调整的结果,够我刷两轮新闻;现在基本是“刚切回 IDE 就刷新好了”。
不过也别把它想得太神。它不会缩短首次冷启动的时间,也不会让你绕过模拟器本身的运行开销。界面越复杂,重组范围越大,刷新速度会稍微变慢,但整体远快于重新编译。
另一个限制是Release 构建默认关闭。我之前为了验证线上性能,切到 release 包调样式,发现 Hot Reload 毫无响应,折腾半天才意识到 release 构建本来就不带热重载代理。后来我学乖了,调 UI 永远用 debug 包。
2.4 状态保留是个精细活
官方宣传稿说“状态保留”,但实际操作中,状态保留是有层次的。
我用表单页做过一次实验:页面上有个输入框,TextField的值保存在rememberSaveable里。我修改按钮的文字,热重载后输入框里的文字还在;修改TextField本身的label,文字还在;但当我改动了remember这个调用点上方的代码结构,比如把var input by rememberSaveable { mutableStateOf("") }换成var input = rememberSaveable(init = { "" })这种写法调整,热重载会判定为 key 结构变化,然后重建这个状态,输入内容被清空。
这给我们的提示是:热重载保留的是“状态对象”,不是“状态的生命周期”。只要你不碰 remember 的 key,状态就还在;一旦 key 变了,状态必然重建。这跟普通重组遇到 key 变化会放弃旧状态是一个道理,只是热重载把这种变化发生在了你眼皮底下。
所以实际开发时我会刻意把状态来源和 UI 表示拆开:ViewModel负责持有状态,Composable 只负责读取和展示。这样改 UI 层的时候,状态来源根本不会被动到,热重载的自然保留率就特别高。
3. 收费方案:价格、边界和我的账本
3.1 我看到的分层定价
收费消息出来之后,我第一反应是找具体的价格方案。官方没有把所有东西一刀切,而是做了分层。
以我看到的订阅条款来说,大概是这样的结构:
- 基础版(免费):保留原有的实验性 Live Edit,静态预览、布局检查器都可以用,但真正的运行时热重载不可用。
- 个人专业版:完整的 Hot Reload 功能,按年订阅,我看到的折算价格大约是一年 129 美元,支持一台开发机器,最多连接两台测试设备。
- 团队版:按席位订阅,每个席位每月 18 美元左右,按年付。支持团队共享的索引缓存、集中式配置,以及构建节点上的预索引分发。
- 教育/开源项目:可以申请免费许可,审核通过后获得与个人专业版等同的能力。
这个价格一出,很多人的反应集中在两点:一是个人开发者一年一千多人民币,到底值不值;二是官方功能收费,是不是意味着以后更多能力都会被拆出来单卖。
3.2 免费与收费的边界在哪里
我仔细比对了免费版和专业版的能力边界,发现划定逻辑其实很清楚:凡是“运行时和代码热交互”的能力都收费,凡是“静态查看”的能力都免费。
比如免费的预览面板能让你把 Composable 的样子画在 IDE 里,那是纯静态渲染,跟真机运行无关。免费的布局检查器能看当前界面的层级和属性,那只是读取已有信息。但只要你让代码变更实时作用到正在运行的 App 里,不管是模拟器、真机,还是调试进程,统统属于 Hot Reload 的范畴,需要专业版权限。
这个边界划分得比较“技术正确”,但用户感知上就很直接:以前能用免费方案凑合的,现在有一道付费墙卡在“真机实时预览”这一个最爽的体验上。
还有个细节要注意:如果团队里只有一个人买了团队版,其他成员的 Hot Reload 是不可用的。它按席位授权,不是按项目授权。我在的团队最开始以为“买一个团队版整个项目组都能用”,后来发现不是这样,又补了几个席位。
3.3 这笔钱其实可以做一道时间账
我个人觉得,值不值不能只看价格,要看它省下来的时间。我给自己算了一笔账。
按我目前的开发节奏,一天里需要反复调整 UI 的次数大约在 20 到 30 次。以前每次等全量编译,乐观估计平均 30 到 40 秒;用了 Hot Reload,一次平均 2 秒。单次看起来只省了 30 秒,但一天下来就省了大约 15 分钟,一个月按 22 个工作日算,就是 5.5 小时。
一年下来,这个数字接近 60 个小时。如果按一个中级开发者的工时成本折算,一小时几十块钱的成本,一年省下的时间价值早就覆盖了订阅费。更不用说长期等待编译对注意力的消耗,那种“改一行字就要切出去等编译”的碎片化状态,是很伤节奏的。
所以如果是在公司项目里用,我倾向于直接买团队版,这不是冲动消费,而是效率投资。
3.4 谁该为它买单
我自己的结论比较明确:
- 个人非主力项目:不买。用基础版凑合,或者干脆用第三方开源方案顶上。
- 个人主力商业项目:看收入。如果项目本身在产生收益,买个人专业版;如果只是练手或开源,申请开源许可。
- 团队多人协作项目:值得买,但建议只给核心 UI 开发买席位,其他成员用基础版,毕竟不是每个人每天都在高频调整界面。
如果项目测试量很大、或者主要工作是写业务逻辑而不是打磨 UI,Hot Reload 带来的边际收益其实有限。买之前先看看自己的日常工作量,别跟风。
4. 社区第一周讨论:真香与破防的两种声音
4.1 反对派在担心什么
收费消息放出来的第一周,开发者社区里吵得很凶。反对的声音倒不是针对功能本身,而是针对“官方开始为开发体验功能收费”这个先例。
持有这种观点的开发者,理由是:Compose 生态之所以能发展起来,很大程度是靠免费工具链降低了上手门槛。现在把最爽的调试体验拆出来收费,虽然不至于伤筋动骨,但“官方免费工具的信任感”被动摇了。
另一个被反复提到的担忧是滑坡效应:今天 Hot Reload 收费,明天会不会把编译器优化、状态保存、甚至Compose核心库里的某个高级特性都拆出来收费?虽然目前来看核心库还是免费开源的,但这种预期上的不确定性,确实让一些团队开始犹豫要不要深度依赖官方工具链。
4.2 第三方方案意外活了
比较有意思的是,在官方收费之后的这一周里,社区里原本不温不火的第三方热更新方案忽然活跃了起来。
我试过一个维护了挺久的开源项目,它走的是另一套技术思路:完全依赖 debug 构建的类加载机制,手动触发一次“应用变更但不清状态”的重组指令。它的能力边界比官方方案窄,比如不支持跨模块增量、不支持资源热更新,但好在完全免费,而且接入成本极低——加一个依赖,在 IDE 里按一个快捷键就能触发。
这个项目在一周之内 star 数涨了很多,很多都是为了避开订阅费的开发者。我也顺手试了试,感觉是:
| 对比项 | 官方 Hot Reload | 第三方开源方案 |
|---|---|---|
| 单文件 UI 改动 | 1 到 3 秒自动生效 | 需要手动触发,约 3 到 5 秒 |
| 资源热更新 | 支持 | 不支持 |
| 跨模块增量 | 支持 | 不支持 |
| 状态保留 | 大部分场景保留 | 大部分场景保留 |
| 费用 | 订阅制 | 免费 |
| 长期维护性 | 官方持续跟进 | 取决于社区活跃度 |
如果你被价格卡住了,第三方方案绝对值得一试,尤其是个人项目。
4.3 我自己的折中配置
目前我自己是混合用的:公司主力项目买了官方团队版,日常连模拟器调 UI 就用官方 Hot Reload;个人开源项目的调试配置换成第三方开源方案,够用,也不花钱。
这种折中的好处是两边的坑我都踩了一遍,不会被单一方案绑死。比如第三方方案在关闭索引时会有随机失败,需要手动重连;官方方案虽然稳定,但有时候会静默吞掉某些资源文件的变更(后面我会细讲)。两边各有脾气,都了解一些,才能在不同场景下快速切到可用的状态。
5. 想用得爽,这五个坑必须先知道
5.1 多模块与动态索引
多模块工程是 Hot Reload 最容易出问题的地方,也是最容易被忽略的。
我最初只在app模块开了hotReload.setEnabled(true),结果改feature:home里的代码完全没有热更新效果,IDE 提示“当前文件所在模块未启用索引”。后来我把所有业务模块都加上了开关,但问题又来了:改一个模块里的代码,其他模块也要重新生成索引,连接时间暴涨。
最后找到一个相对好的配置:开启增量索引模式,并且只给需要高频改代码的 UI 模块开启热重载,像core:network、core:datastore这种纯逻辑模块保持关闭。这样连接时只同步必要的模块索引,速度又快又稳。
5.2 资源文件的“静默失败”
Hot Reload 对资源文件的支持是有限制的,而且这个限制藏得比较深。
我遇到过几次:改了strings.xml里的字符串,模拟器界面纹丝不动,IDE 也不报错,就像这个改动从未发生过。反复试了几次才发现,一部分资源变更不会被热重载代理捕获,尤其是那种只在代码里通过stringResource(R.string.xxx)间接引用的字符串。如果改用硬编码字符串测试,又能立刻生效。
这个坑非常隐蔽,因为它不会给你任何错误提示。我的对策是:改动资源文件之后,如果 5 秒内没看到效果,直接手动触发一次完整刷新,不要傻等。如果你依赖资源热更新来做国际化文案调试,建议还是保守一点,改完马上看,不行就全量编译。
5.3 Flavor 切换和旧连接残留
项目里配置了多个buildType或者 product flavor 的时候,Hot Reload 的连接逻辑会变得有点绕。
我在一个 demo 工程里从devflavor 切到stagingflavor 再跑起来,Hot Reload 一直显示“等待连接”。查了半天才发现,旧 flavor 的代理进程还占着之前的调试端口,新 flavor 的代理连不上。
解决办法很粗暴但有效:切换 flavor 之后,先把 IDE 的连接断开一次,然后在运行配置里重新选择 flavor 再启动。跑起来之后如果还是等待状态,就到终端里把残留的代理进程清理掉再连一次。这个操作我熟悉之后基本 10 秒能搞定,但第一次遇到会卡很久。
5.4 记住状态不等于一切保留
前面说状态保留有边界,这里再补充一个细节:就算状态保留住了,可恢复状态和不可恢复状态的处理也不一样。
rememberSaveable的状态走的是序列化存储通道,热重载之后通常能完整恢复;但用remember保存的非序列化对象,比如一个内存中的列表、一个监听器实例,在热重载时遵循的又是另一套规则,有时会保留,有时会被判定为“需要重新初始化”。
我的建议是:如果你发现热重载后某个页面的状态被清了,优先检查那个状态是不是定义在@Composable函数内部的remember里,而且恰好被热重载判断为 key 结构变化。如果确实需要长期保留,把它挪到ViewModel或者外部持有者那边去。
5.5 热重载时代的代码组织建议
用了一周之后,我最大的感受不是“热重载帮我省了多少时间”,而是它改变了我写代码的方式。
以前为了减少编译次数,我会把很多 UI 相关的代码塞在一个大 Composable 里,想着“反正都要等编译,一次改完再一起跑”。现在热重载很快,我反而开始拆函数了:页面级 Composable 只管组装,子组件拆成一个个独立的小函数,函数签名保持稳定,只改内部实现。
这样的代码组织方式对热重载特别友好,因为每次只改一小块,函数签名不变,状态来源在外层,热重载就能以最小的重组范围完成更新。而且这种写法的可读性和可测试性也明显更好。
如果你准备用 Hot Reload,我建议顺手把代码组织习惯调整一下:保持函数签名稳定、状态尽量上提、UI 拆成纯函数。这一套做法在热重载和不热重载的场景下都成立,属于那种“做了不亏”的长期投资。
我在实际使用中最深刻的体会是,Hot Reload 不只是一项省时间的工具,它改变了 UI 开发和调试的节奏感。以前写界面是一个“改一次、等一次、看一次”的循环,现在更像是在一个活着的画布上直接调色。收费这件事确实是阻力,但从效率产出来看,对于高频调 UI 的场景,它值这个价。对于低频用户,第三方方案也足够撑起日常开发。我的建议就一句话:先试用,再算账,别只看价格标签。