[译] 深入探究 Jetpack Compose UI的实时编辑(Live Edit)

原文地址:Deep dive into Live Edit for Jetpack Compose UI

原文作者: Alan Leung, Staff Software Engineer, Fabien Sanglard, Senior Software Engineer, Juan Sebastian Oviedo, Senior Product Manager

近距离了解 Android Studio 团队如何构建 Live Edit;该功能可在代码更改时持续更新运行中的应用程序,从而加快 Compose 的开发过程。

什么是实时编辑(Live Edit),它如何帮助我?

Live Edit 引入了一种编辑应用程序 Jetpack Compose UI 的新方法,即通过立即将代码更改部署到物理设备或模拟器上正在运行的应用程序。这意味着您可以对应用程序的 UI 进行更改,并立即看到它们对正在运行的应用程序的影响,从而使您能够更快地进行迭代并提高开发效率。Live Edit 最近已通过 Android Studio Giraffe 发布到稳定频道,并且可以在编辑器设置中启用。Plex 和 Pocket Casts 等开发人员已经在使用 Live Edit,它加快了 Compose UI 的开发流程。它还帮助他们从 XML 视图迁移到 Compose。

Android Studio Hedgehog 上的 Live Edit

什么时候我应该使用 Live Edit ?

Live Edit 是与 Compose PreviewApply Changes 不同的功能。这些功能以不同的方式提供价值:

功能 描述 何时使用?
Live Edit [仅限 Kotlin,支持实时重组] 对您的 Compose 应用程序的用户界面进行更改,并立即查看其对模拟器或物理设备上正在运行的应用程序的影响。 快速查看 UX 元素更新(如 modifier 更新和动画)对应用程序运行时整体应用体验的影响。
Compose Preview [仅 Compose ]在 Android Studio 的 "Design" 选项卡中将 Compose 元素可视化,并在您修改代码时看到它们自动刷新。 以一种或多种不同的配置和状态(如暗色主题、本地化和字体比例)预览单个 Compose 元素。
Apply Changes 将代码和资源更新部署到运行中的应用程序,而无需重启该应用程序,在某些情况下,也无需重启当前 activity 。 在非Compose应用程序中更新代码和资源,无需重新部署到模拟器或物理设备中。

它的实现原理 ?

在高层次上,Live Edit 会执行以下操作:

  1. 检测源代码更改
  2. 编译更新的类
  3. 将编译好的新类推送到设备上
  4. 为每个类方法字节码添加钩子,以重定向调用到新的字节码。
  5. 修改应用程序的 classpath ,以确保更改在应用程序重新启动时更改仍然存在。

Live Edit architecture

Keystroke detection 击键检测

这个步骤是通过 Intellij IDEA 的 Program Structure Interface (PSI) 树来处理的,监听器使得 Live Edit 能够检测到开发者在Android Studio编辑器中进行更改的时刻。

编译

从根本上来说,Live Edit 仍然依赖 Kotlin compiler 为每个增量更改生成代码。

我们的目标是创建一个系统,从最后一次按键到设备上发生重组之间的延迟小于 250 毫秒。传统意义上的增量构建或调用外部编译器无法满足我们的性能要求。相反,Live Edit 利用了 Android Studio 与 Kotlin compiler 的紧密集成。

在最高层面上,Kotlin编译器的编译可以分为两个阶段。

  • 分析(Analysis)
  • 代码生成 (Code generation)

作为第一步执行的分析并非完全限定于构建过程。事实上,同样的步骤经常在构建系统之外作为 IDE 的一部分完成。从基本语法检查到自动完成建议,IDE 不断执行相同的分析(上图的步骤 1)并缓存结果,以便为开发人员提供 Kotlin 和 Compose 特定的功能。我们的实验表明,大部分编译时间都花在构建过程中的分析阶段。Live Edit 使用这些信息来调用 Compose 编译器。这使得开发人员使用的典型笔记本电脑可以在 200 毫秒内完成编译。Live Edit 进一步优化了代码生成过程,并仅专注于生成更新应用程序所需的代码。

结果是一个普通的 .class 文件(不是 .dex 文件),该文件被传递到管道中的下一步,即 desugaring

如何脱糖(desugar)

当构建系统处理 Android 应用程序源代码时,通常会在编译后对其进行 "desugared" 处理。这一转换步骤允许应用程序在一组没有语法糖支持和最新 API 功能的 Android 版本上运行。这样,开发人员就可以在应用程序中使用新的 API,同时还能让运行旧版本 Android 的设备使用这些 API。

两种类型的脱糖,分别称为语言脱糖(language desugaring)和库脱糖(library desugaring)。这两种转换都由R8执行。为了确保注入的字节码与设备上当前运行的字节码一致,Live Edit 必须确保每个类文件的脱糖方式与构建系统采用的脱糖方式保持兼容。

语言脱糖(Language desugaring):

这种字节码重写旨在为低版本的API设备提供更新的语言特性。其目标是支持诸如默认接口方法、lambda 表达式、方法引用等语言特性,以便支持最低API级别。这个值是通过 R8 在 .apk 文件的 DEX 文件中留下的标记提取出来的。

API脱糖(API desugaring):

也叫做库脱糖,它旨在支持JAVA SDK 中的方法和类。这是通过一个 JSON 文件进行配置的。除此之外,方法调用点被重写为位于desugar library(该库也嵌入在应用程序中的 DEX 文件中) 中的目标函数。为了执行这个步骤,Gradle 与 Live Edit 合作,提供用于脱糖需要的 JSON 文件。

方法跳板(Function trampoline)

为了方便对运行中的应用程序进行快速的 "per-key-stroke" 速度更新,我们决定不使用 Android Runtime (ART) 的 JVMTI codeswap 能力来处理每一个编辑操作。相反,JVMTI 只被用于一次代码交换,将方法跳板安装到虚拟机内即将修改的类的子集中。我们使用了一个被称为 "Primer" 的东西(上图步骤3),将方法的调用重定向到一个专门的解释器。当应用程序一段时间内没有接收到更新时,Live Edit 将使用传统的 DEX 代码替换代码,以提高 ART 的性能优势。这样一来,开发者就可以节省时间,随着代码的更改,即时更新正在运行的应用程序。

方法跳板处理 Function trampoline process

代码如何被解释

Live Edit 可以动态编译代码。生成的 .class 文件会被推送、重定向(如之前所述),然后在设备上进行解释。这个解释过程是由 LiveEditInterpreter执行的。这个解释器不是在 ART 内部建立的完整虚拟机,而是基于 ASM Frame 构建的帧解释器( Frame interpreter )。ASM Frame 处理低级别的逻辑,比如堆栈/本地变量的推送/加载,但需要一个解释器来实际执行操作码。这就是 OpcodeInterpreter 的作用。

Live Edit 的解释流程

Live Edit Interpreter 是一个简单的循环,它驱动 ASM/Interpreter 操作码的解释执行。

一些 JVM 指令无法使用纯 Java 解释器实现(特别是 invokeSpecialmonitorEnter/Exit 指令存在问题)。对于这些指令,Live Edit 使用 JNI 进行解释。

处理 lambda

Lambda 表达式的处理方式有所不同,因为 lambda 捕获的更改可能会导致 VM 类发生更改,而这些更改在许多方法签名中都不同。因此,新的与 lambda 相关的更新会作为新类发送到正在运行的设备上,而不是像前面一节所述那样重新定义任何已加载的现有类。

重组如何进行?

开发者希望有一种无缝的、无障碍的新方法来编写 Android 应用程序。Live Edit 的关键体验之一是在开发者持续编写代码的同时看到应用程序的更新,而无需明确地按下按钮触发重新运行。我们需要一个 UI 框架,能够监听应用程序中的模型更改并相应地执行最优的重绘。幸运的是,Jetpack Compose 完美地适合这项任务。通过 Live Edit,我们为响应式编程范式增加了另一个维度,即框架还观察函数代码的更改。

为了方便监视代码修改,Jetpack Compose 编译器向 Android Studio 提供了将函数元素映射到一组重组组合的映射。附加的 JVMTI 代理以异步方式使已更改函数的 Compose 状态无效,并且 Compose Runtime 对无效的 Composables 执行重新组合。

重组期间的错误如何处理?

Live Edit 处理运行时错误

虽然持续更新应用程序的概念非常令人兴奋,但我们的实地研究表明,有时当开发者编写代码时,程序可能处于不完整的状态,更新和重新执行某些函数可能会导致不良结果。除了几乎连续发生更新的自动模式之外,我们还为希望对检测到新代码后何时更新应用程序进行更多控制的开发人员引入了两种手动模式。

即使如此,我们仍要确保由于执行不完整的函数而导致的常见问题不会导致应用程序提前终止。Live Edit 会检测循环的退出条件仍在编写的情况,以避免程序内出现无限循环。此外,如果 Live Edit 更新触发重组并导致抛出运行时异常,则 Compose Runtime 将捕获此类异常并使用最后一次已知的良好状态进行重组。

考虑以下代码片段:

var x = y / 10

假设开发者想通过删除字符 1 并在后面插入字符 5 来将 10 更改为 50。Android Studio 可能会在插入 5 之前更新应用程序,从而创建一个除以零的 ArithmeticException。但是,通过上述的的对错误处理功能,应用程序将简单地恢复为 y / 10,直到在编辑器中进行进一步更新。

即将发生什么?

Android Studio 团队相信 Live Edit 将以积极的方式改变 UI 代码的编写方式,并致力于不断改进 Live Edit 的开发体验。我们正在努力扩展开发者可以执行的编辑类型。此外,未来版本的 Live Edit 将消除在某些情况下需要使整个应用程序无效的需要。

此外,PSI 事件检测存在限制,例如当用户编辑 import 语句时。为解决这个问题,未来版本的 Live Edit 将依赖于 .class 比较来检测更改。最后,完整的持久化功能目前尚不可用。未来版本的 Live Edit 将允许在 Android Studio 之外重新启动应用程序并保留 Live Edit 更改。

开始使用 Live Edit

Live Edit 已经可以在生产环境中使用,我们希望它能够极大地改善您在 Android 上开发的体验,尤其是在 UI-heavy 迭代方面。我们非常乐意听取您更多关于有趣的使用案例、最佳实践、错误报告和建议

如果您在阅读中发现有翻译不准确的地方,请随时联系我更改。

相关推荐
技术无疆3 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
茜茜西西CeCe13 小时前
移动技术开发:登录注册界面
java·gitee·gradle·android studio·安卓·移动技术开发·原生安卓开发
技术无疆1 天前
ButterKnife:Android视图绑定的简化专家
android·java·android studio·android-studio·androidx·butterknife·视图绑定
文 丰2 天前
【Android Studio】app:compileDebugJavaWithJavac FAILED解决办法
android·ide·android studio
文 丰2 天前
【Android Studio】2024.1.1最新版本AS调试老项目(老版AS项目文件、旧gradle)导入其他人的项目
android·ide·android studio
我命由我123452 天前
ADB 之 logcat 极简小抄(过滤日志、保存日志到文件)
android·运维·adb·android studio·安卓·运维开发·android-studio
振华OPPO2 天前
我的5周年创作纪念日,不忘初心,方得始终。
android·java·android studio·安卓·安卓app
_Shirley3 天前
android.view.InflateException: Binary XML file line #7: Error inflating class
android·xml·java·ide·kotlin·android studio
叶落无痕523 天前
关于安卓App自动化的一些想法
android·运维·自动化·android studio
Yang-Never3 天前
Android Studio -> Android Studio 获取release模式和debug模式的APK
android·gradle·android studio