查看原文
其他

全面了解实时编辑,即刻加速开发流程

Android Android 开发者 2023-11-14


作者 / 资深软件工程师 Alan Leung、高级软件工程师 Fabien Sanglard、高级产品经理 Juan Sebastian Oviedo 


即刻了解 Android Studio 团队如何构建实时编辑;该功能可随着代码的更改不断更新正在运行的应用,从而加速 Compose 开发流程。


  • 实时编辑

    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/previews

  • Apply Changes
    https://developer.android.google.cn/studio/run#apply-changes


功能

说明

应当在何时使用?

实时编辑
仅限 Kotlin,支持实时重组】更改 Compose 应用的界面,即时在模拟器或实体设备上查看更改对应用运行产生的影响。
在应用运行时,快速查看用户体验元素更新 (例如修饰符更新和动画) 对整体应用体验产生的影响。
Compose 
Preview
仅限 Compose】在 Android Studio 的 Design 标签中可视化 Compose 元素,并在更改代码时查看其自动刷新。
以一种或多种不同配置和状态预览单个 Compose 元素,例如深色主题、语言区域和字体大小。
Apply 
Changes
将代码和资源更新部署到正在运行的应用中,而无需重启,在某些情况下,甚至无需重启当前 Activity。
在非 Compose 应用中更新代码和资源,无需将其重新部署到模拟器或实体设备。

  • 实时编辑
    https://developer.android.google.cn/jetpack/compose/tooling/iterative-development#live-edit

  • Compose Preview
    https://developer.android.google.cn/jetpack/compose/tooling/previews

  • Apply Changes
    https://developer.android.google.cn/jetpack/compose/tooling/iterative-development#apply-changes



该功能是怎样运行的呢?



总体来看,实时编辑可以实现以下功能: 

  1. 检测源代码变更。
  2. 编译已更新的类。
  3. 将新类推送到设备。
  4. 在每个类方法字节码中添加一个钩子 (hook),以将调用重定向至新的字节码。
  5. 编辑应用类路径,确保即使重启应用,更改仍会保留。
△ 实时编辑架构


按键检测


此步骤通过 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


语言脱糖: 

此类字节码重写旨在为目标 API 级别较低的设备提供更新的语言功能。其目标是支持语言功能,例如默认接口方法、lambda 表达式、方法引用等,从而允许支持最低的 API 级别。该值是使用 R8 留下的标记从 .apk 文件中的 DEX 文件里提取的。

  • 语言功能

    https://developer.android.google.cn/studio/write/java8-support#supported_features


API 脱糖: 

此类脱糖又称为库脱糖,旨在支持 JAVA SDK 方法和类。此类脱糖由 JSON 文件配置。除此之外,方法调用网站被重写为位于脱糖库中的目标函数 (该库也嵌入于应用的 DEX 文件中)。要执行此步骤,Gradle 与实时编辑协作,提供库脱糖过程中使用的 JSON 文件。


  • JAVA SDK 方法和类
    https://developer.android.google.cn/studio/write/java8-support-table

 

Trampoline 函数


为促进在 "每次按键" 时快速更新正在运行的应用,我们决定不对每次编辑都持续使用 Android 运行时 (ART) 的 JVMTI 代码交换功能。相反,我们仅在执行代码交换时使用一次 JVMTI,以便将 Trampoline 安装到虚拟机 (VMS) 中即将修改的类中的方法子集上。利用 "Primer",调用方法被重定向到专门的解释器。当应用在一段时间内不再更新时,实时编辑会将代码替换为传统 DEX 代码,来发挥 ART 的性能优势。这可以在代码更改时立即更新运行的应用,为开发者节省时间。

△ 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.java

  • ASM
    https://asm.ow2.io/javadoc/org/objectweb/asm/tree/analysis/Frame.html

  • 解释器
    https://asm.ow2.io/javadoc/org/objectweb/asm/tree/analysis/Interpreter.html

  • OpcodeInterpreter
    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


△ 实时编辑解释流程
实时编辑解释器是一个简单的循环,用于驱动 ASM/解释器操作码的解释。

  • 循环
    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


某些 JVM 指令无法使用纯 Java 解释器来实现 (尤其是 invokeSpecialmonitorEnter/Exit 存在问题)。对于这种情况,实时编辑使用 JNI。


  • 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 和/或其附属公司的商标或注册商标。




推荐阅读

如页面未加载,请刷新重试


 点击屏末 | 阅读原文 | 即刻了解实时编辑更多相关内容




继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存