0f1ce81cb83e343ed8407bd5d49b5ec1
底层原理 - Flutter Hot Reload 详解

Hot Reload 是什么

在正式开始之前,我们先来了解下 Hot Reload 是什么?对于移动端开发童鞋来说,大部分人第一次听说 Hot Reload 这个词应该都是通过 React Native 了,那我们就先来看一段 React Native 官网对 Hot Reload 的定义:

The idea behind hot reloading is to keep the app running and to inject new versions of the files that you edited at runtime. This way, you don't lose any of your state which is especially useful if you are tweaking the UI.

这段话的意思就是指 App 运行时可以动态注入修改后的文件内容,同时不中断 App 的正常运行。这样,我们就不会丢失 App 的任何状态信息,尤其是 UI 页面栈相关的。

React Native 的 Hot Reload

既然已经讲到 React Native,那我们刚好来研究下它的 Hot Reload。做过 React Native 开发的童鞋应该知道,React Native 的 Hot Reload 是基于 Webpack 的 HMR 来实现的,那什么是 HMR 呢?HMR 又是如何实现 Web 应用正在运行时在无需整个页面 Refresh 的前提下,实现对特定模块替换,添加,或者删除的操作的呢?请看下文。

HMR(Hot Module Replacement)

HMR,模块热加载,这是 React Native 实现 Hot Reaload 的核心,HMR 最早其实是由 Webpack 引入的,React Native 在它的 React Native Packager 中也实现了这个功能。HMR 使得 Packager 可以监控文件的改动并发送 HMR 更新消息(HMR update)给包含在 APP 中的一层很薄的 HMR 运行时(HMR runtime)。

简单来说,HMR 更新消息包含 JS 模块中发生变化的代码,当 HMR 运行时接收到这个消息,就使用新的代码替换旧的代码,流程如下图所示:

当然,上图只是大体的一个流程,其实在各个部分都还有很多细节:比如 HRM API 的使用姿势、HMR Runtime 采用的模块依赖树更新机制、以及 React Component 的状态保存方式,大家可以参考 React Native 官方对 Hot Reload 的介绍文档

Flutter 的 Hot Reload

与 React Native 不同,Flutter 是完全不同的一套 Hot Reload 机制。

AOT 和 JIT

在研究 Flutter 的 Hot Reload 实现原理之前,AOT 和 JIT 是一定要先了解的,因为 Flutter 的 Debug 模式采用的是 JIT 编译,而 Release 模式采用的是 AOT 编译。而且 Flutter 在 Debug 模式下能支持 Hot Reload 的根本原因则正是 JIT 编译。

JIT,全名 Just-In-Time,指在运行时编译,边运行边编译,比如 Java 虚拟机在运行时就用到 JIT 技术
AOT,全名 Ahead-Of-Time,指在运行前编译,比如普通的静态编译。
这两种编译方式的主要区别在于是否在运行时进行编译

AOT 和 JIT 实现原理分析

为了更好的理解这两种编译方式,我们先来了解下两者的实现原理。JIT 和 AOT 归根到原理,其实就是解释执行和编译执行的区别,下面是解释执行和编译执行的实现原理流程图:

AOT 实现原理

AOT 实现原理

JIT 实现原理
JIT 实现原理

通过这两个流程图我们可以发现,AOT 是纯静态编译,不存在运行时编译,而 JIT 则是通过中间代码的方式,在运行时又进行了一步解释执行操作。

AOT 和 JIT 优缺点对比

通过对两者实现原理的分析,我们可以非常明显的得出两者的优缺点,如下:

优点 缺点
JIT 1. 可以根据当前硬件情况实时编译生成最优机器指令(PS:AOT也可以做到,在用户使用是使用字节码根据机器情况在做一次编译);
2. 可以根据当前程序的运行情况生成最优的机器指令序列;
3. 当程序需要支持动态链接时,只能使用 JIT;
4. 可以根据进程中内存的实际情况调整代码,使内存能够更充分的利用。
1. 编译需要占用运行时资源,会导致进程卡顿;
2. 由于编译时间需要占用运行时间,对于某些代码的编译优化不能完全支持,需要在程序流畅和编译时间之间做权衡;
3. 在编译准备和识别频繁使用的方法需要占用时间,使得初始编译不能达到最高性能。
AOT 1. 在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗
;2. 可以在程序运行初期就达到最高性能;
3. 可以显著的加快程序的启动。
1. 在程序运行前编译会使程序安装的时间增加;
2. 牺牲代码的一致性;
3. 将提前编译的内容保存会占用更多的外部存储空间。

上面列出的优缺点也恰恰符合 Flutter 在 Debug 和 Release 模式下对这两种编译方式的选择,Debug 采用 JIT 模式是因为需要快速查看变更,而且对性能上要求不是那么高;而 Release 模式则必须要采用 AOT 了,因为在生产环境下,我们没有那么强烈的动态需求,但是对性能的要求绝对是越高越好。

在 Flutter 上使用 Hot Reload

在介绍原理之前,我们先来看下 Flutter 的 Hot Reload 如何使用。

  • 支持 Flutter 的 IDE(如:Android Studio)或终端窗口运行应用,真机或模拟器都可以。需要注意的是只有 Debug 模式支持 Hot Relaod;
  • 修改项目中的某个 Dart 文件。大多数类型的代码更改可以 Hot Reload,但是也有一些情况是不行的,稍后我们会列举出来;
  • 如果你使用的是支持 Flutter 的 IDE,请选择 Save All (cmd-s/ctrl-s)),或者单击工具栏上的 Hot Reload 按钮。
  • 如果你是在命令行通过 flutter run 运行的应用程序,请在终端窗口输入 r

成功执行热重载后,您将在控制台中看到类似于以下内容的消息:

Performing hot reload...
Reloaded 1 of 448 libraries in 978ms.

不支持 Hot reload 的情况

  • 枚举类型变成普通 Class 类型或者普通 Class 类型变成枚举类型的情况。比如把下面的代码:

    enum Color {
      red,
      green,
      blue
    }

    改成下面的代码:

    class Color {
      Color(this.i, this.j);
      final int i;
      final int j;
    }
top Created with Sketch.