[Flutter翻译]Flutter热重载

214 阅读10分钟

在热重载过程中会发生什么,以及我们如何在 Flutter 2.2 中使其更快。

原文地址:medium.com/flutter/flu…

原文作者:medium.com/@jensj_4162…

发布时间:2021年8月20日 - 7分钟阅读

热重载在Flutter 2.0中很快速,但在Flutter 2.2中更快。请继续阅读关于热重载的一般细节,以及我们如何使其在Flutter 2.2中更快。

image.png

简介

Flutter的伟大功能之一是热重载。你按下键盘上的r,片刻之后,你所做的改变的影响就可以在设备上看到。在你的终端(或者在你的IDE的底部),你可以读到这样的内容:在297ms内重新加载了553个库中的1个。但是,当你启动热重载时,引擎盖下究竟发生了什么,Dart和Flutter团队又是如何让它变得更快?

热重载的概述

Flutter中的热重载大致包括以下五个步骤。

  1. flutter_tools 扫描所需的文件,以寻找变化。它查看每个需要的文件,并检查其最后更改的时间戳是否比之前的编译时间戳要新。
  2. flutter_tools 指示正在运行的 Dart 编译器重新编译应用程序,并告诉它哪些文件已经改变。Dart 编译器会重新编译。
  3. Flutter_tools 将更新的文件发送到设备上。这包括任何改变的资产和新编译的delta内核文件(编译的输出,一个Dart虚拟机可以理解的文件)。
  4. flutter_tools要求设备上Dart虚拟机中的所有隔离器重新加载其源文件(以读取已更改的delta内核文件并执行其魔法)。
  5. flutter_tools指示设备上的应用程序重新组装--重建屏幕上的所有小部件,重新加载资产,重做布局,重新绘制等。

在我的开发者机器上,目标设备是Linux(即在本地运行的桌面应用程序),在一个新创建的flutter创建应用程序上执行第一次热重载,只更新了lib/main.dart文件的时间戳,我得到的时间是这样的(从flutter run -v提取)。

  1. 扫描文件需要 ~13 ms。
  2. 重新编译需要~67 ms。
  3. 发送文件到设备需要~2毫秒。
  4. Dart虚拟机重新加载源代码需要~96毫秒。
  5. 重新组装需要~114毫秒。

如果我有一个更大的应用程序(并改变一些其他文件),我可能会得到这样的时间。

  1. 扫描文件需要~12 ms。
  2. 重新编译需要~386毫秒。
  3. 发送文件到设备需要~2毫秒。
  4. Dart虚拟机重新加载源代码需要~171毫秒。
  5. 重新组装需要~229毫秒。

在这两种情况下,以下步骤花费的时间最多。

  • 重新编译
  • 重新加载
  • 重新组装

为了使热重载更快,我们必须使这三个步骤中的一个或多个步骤更快。 在这里,我将专注于第一部分:将改变的源文件重新编译成Dart虚拟机可以使用的文件。

重新编译

从逻辑上讲,如果我作为一个用户改变了一个文件--比如说,foo.dart--我可能期望重新编译的结果是这样的。

  1. 编译器在内存中拥有旧的状态。
  2. 编译器被告知foo.dart已经改变。
  3. 编译器扔掉了foo.dart的内部状态。
  4. 编译器重新编译foo.dart。
  5. 完成了。

这将是很好的。这意味着无论我修改哪个文件,都只需要重新编译那个文件,而且--大概--重新编译的速度会很快。

  • 不幸的是,重新编译通常并不像那样工作。下面是两个例子,说明为什么重新编译可能不那么简单。 foo.dart曾经包含Foo类,这个类在所有地方都被使用。改变后的文件不包含这个类(也许是手动重命名的),每个使用这个类的文件都应该得到一个编译错误。
  • foo.dart过去有一个字段定义为var z = 42。另一个文件使用这个字段:var z2 = z * 2。Dart类型推理发现z是一个整数,z2是一个整数,因为z是1。现在这个字段改成说var z = 42.2。这次Dart类型推理会发现字段是一个双数,但是如果不重新编译其他库,z2仍然会(错误地)被标记为一个整数。

由于这个原因,Dart的重新编译长期以来是这样的。

  1. 编译器拥有内存中的旧状态。
  2. 编译器被告知foo.dart已经改变。
  3. 编译器扔掉了foo.dart的内部状态。
  4. 编译器检查哪些文件导入或导出了foo.dart,并把它们也扔掉。
  5. 编译器检查哪些文件导入或导出了第4步中的文件,并把这些文件也扔掉。
  6. 继续下去:扔掉所有的递归导入器和导出器。
  7. 编译器重新编译所有(现在)"丢失 "的库。
  8. 完成了。

这可能听起来很糟糕,但在许多情况下,它并不糟糕。尽管改变你自己的自定义部件集可能会导致重新编译你写的所有代码,但它不会导致重新编译Flutter框架本身,例如,因为Flutter框架不会导入或导出你的库。另一方面,如果你改变了一个对Flutter框架至关重要的文件,你最终会重新编译(几乎)所有的东西。

回顾一下这个(不完整的)列表,我们可以看到一个模式,就是为什么仅仅重新编译一个被修改的文件就不能工作了。它不起作用是因为你做了全局性的修改--影响其他库的修改。但如果你只修改了一个注释呢?或者在你的构建方法中添加了另一个调试打印?或者在你的实用方法中修复了一个偏离的错误?这些改变并不是全局性的,我们应该能够做得更好!

做得更好

对于非全局性的变化--不能影响其他库的编译的变化--我们事实上可以只重新编译被改变的库,并且仍然保留语义。主要的问题是要弄清楚什么时候是全局性的改变,什么时候不是(并且要快速完成)。幸运的是,这可以分步进行:我们不需要立即(或根本不需要)使它完美。

第一步可能是将原来的文件与现在的文件进行比较,同时忽略两个版本的文件中的注释。如果这样比较时,两个版本的文件都是一样的,我们就得出结论,没有全局性的变化,我们就继续重新编译单一的变化过的文件,而不是过渡性的进口-出口图。这种技术并不完美。例如,在修复你的实用程序方法中的逐一错误时,它仍然会触发所有横向导入器和导出器的重新编译。但它可以让你在修复注释中的拼写错误的同时,只重新编译那个文件。

这里有一个简单的题外话。如果我们只是修改注释,为什么还要重新编译呢?这主要是因为堆栈痕迹。在内部,一些节点(代表你的代码)包含偏移量--关于它们在文件中的位置的信息。如果这些信息变得过时了,你的堆栈跟踪就会包含无效的信息。例如,它可能声称某些事情发生在第42行,但实际上并没有发生。

为了让你能在只重新编译文件的情况下修复你的实用方法中的一个个错误,我们在检查全局变化时还必须忽略一个东西:函数体。我们将再次比较改变后的文件的前后版本,这一次将忽略注释和函数体。如果它们是一样的,我们就只重新编译那个文件。

现在我们实际上处在一个位置,你可以做一些有用的改变,而不需要重新编译你所改变的文件。你可以添加、删除和以其他方式改变注释。你可以在你的构建方法中添加(或删除)debug-prints。你甚至可以修复你的实用方法中的错误。

好消息!

事实证明,这些对重新编译的改进实际上已经实现了。如果你正在使用Flutter 2.2,你甚至可能已经注意到了它。如果没有,也许你现在会注意到。说实话,对于小的应用程序,你可能不会注意到有多大的速度提升,但对于大的应用程序,你应该注意到。

我做了几个非全局变化的例子来衡量其效果。

对于Veggie Seasons示例应用程序(一个相对较小的应用程序)。

  • 改变lib/main.dart没有带来任何改善。它以前编译了一个文件,现在编译了一个文件。
  • 改变lib/data/veggie.dart会有30%左右的改进。我的电脑上的实际编译时间从100多毫秒到<20毫秒(以前编译18个文件,现在只编译一个文件)。这自然远远超过了30%,但由于重新编译只是三个时间汇中的一个(另外两个是重新加载和重新汇编),整体变化在30%左右。

对于Flutter Gallery(一个相对较大的应用程序)。

  • 改变lib/main.dart会产生非常小的改进(它编译了1个文件而不是2个)。
  • 改变lib/layout/adaptive.dart的结果是重新加载的时间几乎减半。仅仅重新编译的时间就从近400毫秒变成了40毫秒(重新编译1个文件而不是47个文件)。

你应该期望现实世界的热重载,在Flutter 2.2中平均比Flutter 2.0中快30%左右。从这个角度来看,这一变化为Flutter开发人员节省了一年多的时间,即每5天等待一次热重载。

注意事项

我们对热重载的改变并不总是意味着编译器的工作减少。例如,如果你增加或删除一个方法,编译器不会做更少的工作。如果你改变了一个字段的初始化器,编译器不会做更少的工作。如果你改变了类的层次结构,编译器也不会减少工作。如果你改变了一个函数的主体--在这种情况下,编译器通常应该做更少的工作--因为围绕着混合函数和FFI的技术问题,编译器可能仍然需要做同样多的工作。

另外,当我们谈到比较文件时,我们跳过了一些技术细节。首先,我们不能忽略每一个注释:我们需要保留@dart版本标记,因为它有语义。其次,我们不能忽略每一个函数体,因为围绕混合函数和FFI的具体实施挑战。

总结

热重载在Flutter 2.0中是很快的,但在Flutter 2.2中更快。平均而言,Flutter 2.2中的热重载比Flutter 2.0快30%左右,这为Flutter开发者节省了一年多的时间,即每5天等待一次热重载。 如果你还没有更新(或者甚至还没有尝试过Flutter),现在可能是访问flutter.dev并尝试一下的好时机。


www.deepl.com 翻译