全面了解实时编辑,即刻加速开发流程
作者 / 资深软件工程师 Alan Leung、高级软件工程师 Fabien Sanglard、高级产品经理 Juan Sebastian Oviedo
实时编辑
https://developer.android.google.cn/jetpack/compose/tooling/iterative-development#live-edit
什么是实时编辑?
对我有何帮助?
实时编辑引入了一种编辑应用 Jetpack Compose 界面的新方法,即实时将代码更改部署到实体设备或模拟器上运行的应用中。也就是说,您能够更改应用界面并即时查看更改对应用运行产生的影响,从而更快地迭代,提高开发效率。实时编辑最近已通过 Android Studio Giraffe 发布稳定版,您可在编辑器设置中启用。Plex 和 Pocket Casts 等开发者已经开始使用实时编辑,这加快了 Compose 界面的开发流程。在 XML 视图迁移到 Compose 的过程中,实时编辑也发挥了作用。
△ 在 Android Studio Hedgehog 上进行实时编辑
在什么情况下
应该使用实时编辑?
实时编辑功能与 Compose Preview 及 Apply Changes 不同。这些功能以不同的方式提供价值:
Compose Preview
https://developer.android.google.cn/jetpack/compose/tooling/previewsApply Changes
https://developer.android.google.cn/studio/run#apply-changes
功能 | 说明 | 应当在何时使用? |
实时编辑
https://developer.android.google.cn/jetpack/compose/tooling/iterative-development#live-editCompose Preview
https://developer.android.google.cn/jetpack/compose/tooling/previewsApply Changes
https://developer.android.google.cn/jetpack/compose/tooling/iterative-development#apply-changes
该功能是怎样运行的呢?
总体来看,实时编辑可以实现以下功能:
检测源代码变更。 编译已更新的类。 将新类推送到设备。 在每个类方法字节码中添加一个钩子 (hook),以将调用重定向至新的字节码。 编辑应用类路径,确保即使重启应用,更改仍会保留。
按键检测
此步骤通过 Intellij IDEA 程序结构接口 (PSI) 树进行处理。监听器允许 LE 在 Android Studio 编辑器中检测到开发者进行更改的时刻。
编译
根本上,实时编辑仍然依赖 Kotlin 编译器为每个增量更改生成代码。
我们的目标是创建一种系统,使最后一次按键到设备上重新编译的延迟时间小于 250 毫秒。典型的增量构建,或调用传统意义上的外部编译器都将无法满足我们的性能要求。相反,实时编辑利用了 Android Studio 与 Kotlin 编译器进行紧密集成。
总体来看,Kotlin 编译器的编译过程可分为 2 个阶段。
分析
代码生成
第 1 步执行的分析不完全受限于构建流程。事实上,相同步骤经常在构建系统外作为 IDE 的一部分完成。从基本语法检查到自动完成建议,IDE 持续执行相同分析并缓存结果,为开发者提供 Kotlin 和 Compose 的特定功能。实验表明,编译的大部分时间消耗在构建过程中的分析阶段。实时编辑利用这一信息,调用 Compose 编译器。因此,开发者使用常用的笔记本电脑可在 200 毫秒内完成编译。实时编辑进一步优化了代码生成过程,并仅专注于生成更新应用所需的代码。
其结果是一个普通的 .class 文件 (不是 .dex 文件),该文件被传递到流水线中的下一步,即脱糖。
脱糖方式
构建系统处理 Android 应用源代码,通常会在编译后进行 "脱糖"。此转换步骤允许应用在一组缺乏语法糖支持和最新 API 功能的 Android 版本上运行。因此,开发者可以在应用中使用新 API,同时确保应用仍可以运行在旧版本的 Android 设备上。
脱糖
https://developer.android.google.cn/studio/write/java8-support-table
脱糖有 2 种类型,即语言脱糖和库脱糖。这 2 种转换都是由 R8 执行的。为确保注入的字节码与设备上当前运行的内容匹配,实时编辑必须保证每个类文件的脱糖方式和构建系统的脱糖方式兼容。
脱糖有 2 种类型
https://developer.android.google.cn/studio/write/java8-support
语言脱糖:
语言功能
https://developer.android.google.cn/studio/write/java8-support#supported_features
此类脱糖又称为库脱糖,旨在支持 JAVA SDK 方法和类。此类脱糖由 JSON 文件配置。除此之外,方法调用网站被重写为位于脱糖库中的目标函数 (该库也嵌入于应用的 DEX 文件中)。要执行此步骤,Gradle 与实时编辑协作,提供库脱糖过程中使用的 JSON 文件。
JAVA SDK 方法和类
https://developer.android.google.cn/studio/write/java8-support-table
Trampoline 函数
解释代码的过程
实时编辑会即时编译代码。生成的 .class 文件会被推送、执行 trampoline (如前所述),然后在设备上进行解释。此解释步骤由 LiveEditInterpreter 执行。解释器并非 ART 内部的完整虚拟机,而是构建在 ASM Frame 之上的 Frame 解释器。ASM Frame 处理低级逻辑,例如堆栈/局部变量的推送/加载,但是 ASM Frame 需要解释器来实际执行操作码。这正是 OpcodeInterpreter 的用途。
LiveEditInterpreter
https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:deploy/agent/runtime/src/main/java/com/android/tools/deploy/interpreter/ByteCodeInterpreter.javaASM
https://asm.ow2.io/javadoc/org/objectweb/asm/tree/analysis/Frame.html解释器
https://asm.ow2.io/javadoc/org/objectweb/asm/tree/analysis/Interpreter.htmlOpcodeInterpreter
https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:deploy/agent/runtime/src/main/java/com/android/tools/deploy/interpreter/OpcodeInterpreter.java;drc=dacb8a41a53d1aa21be7e9c5467c77fcacce9632
循环
https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:deploy/agent/runtime/src/main/java/com/android/tools/deploy/interpreter/ByteCodeInterpreter.java
JNI
https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:deploy/agent/runtime/src/main/java/com/android/tools/deploy/interpreter/JNI.java
处理 Lambda
Lambda 有不同的处理方式,因为对 Lambda 捕获的更改可能导致 VM 类发生改变,这种改变在许多方法签名中都不同。相反,新 Lambda 相关更新将被发送到运行设备,并作为新类加载,而非如上文所述,重新定义任何现有的加载类。
如何进行重组?
开发者需要一种无缝且顺畅的 Android 应用编程新方式。实时编辑体验的关键部分,是开发者不断编写代码时可以看到应用的更新,而无需再按下按钮来触发重新运行。我们需要界面框架能够监听应用内的模型更改,并可以相应进行最优重绘。幸运的是,Jetpack Compose 非常合适。通过实时编辑,我们为响应式编程范式添加了一个额外的维度,即框架还可以观察函数代码的变化。
为方便代码修改监控,Jetpack Compose 编译器为 Android Studio 提供了函数元素到一组重组组键的映射。附加 JVMTI 代理以异步方式让变更函数的 Compose 状态无效,并且 Compose 运行时对无效的 Composable 执行重组。
如何在重组期间处理运行时错误?
△ 实时编辑处理运行时错误
虽然持续更新应用的概念令人振奋,但我们的实地研究表明,有时开发者在编写代码时,程序可能处于不完整的状态,此时更新和重新执行某些函数会导致不理想的结果。除了几乎连续更新的自动模式外,我们还引入了 2 种手动模式,方便那些希望在新代码被检测到后控制应用于何时更新的开发者。
即使考虑到这一点,我们仍然希望能够避免执行不完整函数带来的常见问题导致应用提前终止的情况发生。实时编辑会检测仍在编写循环退出条件的情况,来避免程序内出现无限循环。此外,如果实时编辑更新触发重组,并导致运行时异常被抛出,则 Compose 运行时将捕获此异常,并使用最后已知的良好状态重组。
您可以考虑以下代码:
var x = y / 10
假设开发者希望删除字符 1,再插入字符 5,从而将 10 更改为 50。Android Studio 可能会在插入 5 之前更新应用,因此导致了零除的 ArithmeticException。但是,有了上述添加的错误处理,应用将仅会恢复为 "y / 10",直到编辑器中完成了进一步更新。
即将发生什么?
Android Studio 团队相信,实时编辑将会以积极的方式改变界面代码的编写,我们致力于不断优化实时编辑的开发体验。我们正在扩展开发者可执行的编辑类型。此外,实时编辑的未来版本将排除需要使整个应用无效的情况。
另外,PSI 事件检测还存在一些限制,例如在用户编辑导入语句的时候。为解决这一问题,实时编辑未来版本将依赖 .class diffing 来检测更改。最后,完整的保留功能当前尚不可用。实时编辑未来版本将允许应用在 Android Studio 外重启,并保留实时编辑的更改。
即刻开始使用实时编辑
实时编辑已准备就绪,可在生产环境中使用,我们希望实时编辑能够大大改善您的 Android 开发体验,尤其是针对界面密集型迭代。我们希望能够更好地了解您,欢迎您向我们分享有趣用例、最佳实践、错误报告以及建议。也欢迎您持续关注 "Android 开发者" 微信公众号,及时了解更多开发技术和产品更新等资讯动态。
实时编辑
https://developer.android.google.cn/jetpack/compose/tooling/iterative-development#live-edit-quickstart错误报告以及建议
https://developer.android.google.cn/studio/report-bugs
*Java 是 Oracle 和/或其附属公司的商标或注册商标。
推荐阅读