以下是 Dart 功能的快速列表,它们共同使 Flutter 不可或缺:

  • Dart 是 AOT(Ahead Of Time)编译成快速、可预测的原生代码,它允许几乎所有 Flutter 都可以用 Dart 编写。这不仅使 Flutter 速度更快,而且几乎所有东西(包括所有小部件)都可以自定义。
  • Dart 也可以进行 JIT(及时)编译,以实现极快的开发周期和改变游戏规则的工作流程(包括 Flutter 流行的亚秒级状态热重载)。
  • Dart 可以更轻松地创建以 60fps 运行的流畅动画和过渡。Dart 可以在没有锁的情况下进行对象分配和垃圾回收。和 JavaScript 一样,Dart 避免了抢占式调度和共享内存(因此也避免了锁)。因为 Flutter 应用程序被编译为原生代码,它们不需要领域之间的慢速桥梁(例如,JavaScript 到原生代码)。它们的启动速度也快得多。
  • Dart 允许 Flutter 避免需要单独的声明式布局语言(如 JSX 或 XML)或单独的可视化界面构建器,因为 Dart 的声明式、程序化布局易于阅读和可视化。并且所有布局都使用一种语言和一个地方,Flutter 很容易提供高级工具,使布局变得轻而易举。
  • 开发人员发现 Dart 特别容易学习,因为它具有静态和动态语言用户都熟悉的特性。

并非所有这些功能都是 Dart 独有的,但它们的组合达到了一个*甜蜜点*,使 Dart 在实现 Flutter 方面具有独特的强大功能。如此之多,很难想象 Flutter 在没有 Dart 的情况下会如此强大。

image-20220424113925263

Flutter 喜欢 Dart

本文的其余部分将更深入地介绍 Dart 的许多特性(包括其标准库),这些特性使其成为*实现 Flutter 的最佳语言*。

编译和执行

[如果您已经了解静态与动态语言、AOT 和 JIT 编译以及虚拟机等主题,则可以跳过本节。]

从历史上看,计算机语言被分为两类:*静态*语言(例如,Fortran 或 C,其中变量在编译时静态类型化)和*动态*语言(例如,Smalltalk 或 JavaScript,其中变量的类型可以在运行时改变时间)。静态语言通常被编译为为目标机器生成本机机器代码(或*汇编代码*)程序,这些程序在运行时由硬件直接执行。动态语言由解释器执行,不产生机器语言代码。

当然,事情最终变得更加复杂。虚拟机(VM)的概念变得流行起来,它实际上只是一种在软件中模仿硬件机器的高级解释器。虚拟机使将语言移植到新的硬件平台变得更加容易。在这种情况下,VM 的输入语言通常是一种中间语言。例如,一种编程语言(如Java)被编译成一种中间语言(字节码),然后在虚拟机(JVM)上执行。

此外,现在还有即时(JIT) 编译器。JIT 编译器在程序执行期间运行,即时编译。在程序创建期间(运行时之前)执行的原始编译器现在称为提前(AOT) 编译器

一般来说,只有静态语言适合 AOT 编译为本机机器代码,因为机器语言通常需要知道数据的类型,而在动态语言中,类型并没有提前固定。因此,动态语言通常被解释或 JIT 编译。

在开发过程中完成 AOT 编译时,总是会导致开发周期变慢(从对程序进行更改到能够执行程序以查看更改结果之间的时间)。但是 AOT 编译导致程序可以更可预测地执行,并且在运行时无需暂停分析和编译。AOT 编译的程序也开始执行得更快(因为它们已经被编译)。

相反,JIT 编译提供了更快的开发周期,但会导致执行速度变慢或不稳定。特别是,JIT 编译器的启动时间较慢,因为当程序开始运行时,JIT 编译器必须先进行分析和编译,然后才能执行代码。研究表明,如果开始执行的时间超过几秒钟,许多人会放弃应用程序

背景信息到此结束。结合 AOT 和 JIT 编译的优点不是很棒吗?继续阅读。

编译和执行 Dart

在开发 Dart 之前,Dart 团队成员在高级编译器和虚拟机方面做了开创性的工作,包括动态语言(如用于 JavaScript的V8 引擎和用于 Smalltalk 的Strongtalk )和静态语言(如用于 Java的Hotspot 编译器)。他们利用这一经验使 Dart 在编译和执行方面异常灵活。

Dart 是极少数非常适合同时编译 AOT 和 JIT 的语言之一(也许是唯一的“主流”语言)。支持这两种编译为 Dart 和(尤其是)Flutter 提供了显着的优势。

在开发过程中使用 JIT 编译,使用速度特别快的编译器。然后,当应用程序准备好发布时,它会被编译为 AOT。因此,借助先进的工具和编译器,Dart 可以实现两全其美:极快的开发周期,*以及*快速的执行和启动时间。

Dart 在编译和执行方面的灵活性并不止于此。例如,Dart 可以编译成 JavaScript,以便浏览器执行。这允许在移动应用程序和 Web 应用程序之间重用代码。开发人员报告称,他们的移动和 Web 应用程序之间的代码重用率高达 70% 。Dart 也可以通过编译为本机代码或编译为 JavaScript 并与node.js一起使用来在服务器上使用。

最后,Dart 还提供了一个独立的 VM,它使用 Dart 语言本身作为其中间语言(本质上就像一个解释器)。

Dart 可以高效地编译 AOT 或 JIT、解释或转译成其他语言。Dart 不仅编译和执行异常灵活,而且速度特别*快*。

下一节提供了一个示例,说明 Dart 的编译速度如何改变游戏规则……

有状态的热重载

Flutter 最受欢迎的功能之一是其极快的*热重载*。在开发过程中,Flutter 使用 JIT 编译器,通常可以在一秒钟内重新加载并继续执行代码。尽可能在重新加载时保留应用程序状态,因此应用程序可以从中断处继续。

1_c1dM9uhkRj9_fpiDrLJmDw

Flutter 的亚秒级有状态热重载

除非您亲自体验过,否则很难理解真正快速(和可靠)热重载在开发过程中的重要性。开发人员报告说,它改变了他们创建应用程序的方式,将其描述为就像将他们的应用程序描绘成生命一样。

以下是一位移动应用程序开发人员对 Flutter 的热重载的评价

我想测试热重载,所以我改变了颜色,保存了我的修改,然后……坠入爱河❤️!

这个功能真的很神奇。我认为 Visual Studio 中的Edit & Continue很好,但这简直令人震惊。只有这样,我认为移动开发人员的工作效率才能提高两倍。

这对我来说真的是一个改变游戏规则的人。当我部署我的代码并且需要很长时间时,我会失去注意力,我会做其他事情,当我回到模拟器/设备时,我已经忘记了我想要测试的内容。有什么比浪费 5 分钟将控件移动 2px 更令人沮丧的呢?有了 Flutter,这不再存在。

Flutter 的热加载功能使尝试新想法或尝试替代方案变得更加容易,从而极大地激发了创造力。

到目前为止,我们已经讨论了 Dart 如何为开发人员带来更好的体验。下一节将介绍 Dart 如何更轻松地创建令用户满意的流畅应用程序。

避免卡顿

*快速*的应用程序很棒,但*流畅*的应用程序更好。如果是生涩的,即使是超快的动画也会看起来很糟糕。然而,防止卡顿可能很困难,因为有很多不同的原因。Dart 有许多功能可以避免导致卡顿的许多常见问题。

当然,(像任何语言一样)仍然可以在 Flutter 中编写一个 janky 应用程序;Dart 有助于提高可预测性,让开发人员可以更好地控制应用程序的流畅度,从而更轻松地提供可能的最佳用户体验,没有之一。

结果

以 60 fps 运行,使用 Flutter 创建的用户界面的性能远优于使用其他跨平台开发框架创建的用户界面。

不仅比跨平台应用程序好,而且与最好的原生应用程序一样好:

UI 非常流畅……我从未见过如此流畅的 Android 应用程序。

AOT 编译和“桥梁”

我们已经讨论了一个有助于保持流畅的特性,那就是 Dart 能够被 AOT 编译为本机机器代码。预编译的 AOT 代码比 JIT 更可预测,因为在运行时没有暂停来执行 JIT 分析或编译。

然而,AOT 编译代码还有一个更大的优势,那就是避免“JavaScript 桥”。当动态语言(如 JavaScript)需要与平台上的本地代码互操作时,它们必须通过桥进行通信,这会导致上下文切换必须保存特别大量的状态(可能会保存到辅助存储)。这些上下文切换是双重打击,因为它们不仅会减慢速度,还会导致严重的卡顿。

image-20220424114136639

“桥”

注意:即使是编译后的代码也可能需要一个接口来与平台代码对话,这也可以称为桥接器,但它通常比动态语言所需的桥接器快几个数量级。此外,由于 Dart 允许将诸如小部件之类的东西移动到应用程序中,因此减少了过桥的需要。

抢占式调度、时间片和共享资源

大多数支持多个并发执行线程的计算机语言(包括 Java、Kotlin、Objective-C 和 Swift)都使用抢占在线程之间切换。每个线程都被分配了一个“片”来执行,如果超过了分配的时间,线程就会被上下文切换抢占。但是,如果在更新线程之间共享的资源(如内存)时发生抢占,则会导致竞争条件

竞态条件是双重打击,因为它们会导致严重的错误,包括使您的应用程序崩溃和导致数据丢失,并且它们特别难以找到和修复,因为它们取决于独立线程的相对时间。当您在调试器中运行应用程序时,竞争条件停止表现出来是很常见的。

修复竞态条件的典型方法是使用来保护共享资源,该锁阻止其他线程执行,但锁本身会导致卡顿,甚至更严重的问题(包括死锁饥饿)。

Dart 对这个问题采取了不同的方法。Dart 中的线程,称为分离物, 不共享内存,这样就避免了对大多数锁的需要。隔离通过通道传递消息进行通信,类似于*演员*在二郎或者*网络工作者*在 JavaScript 中。

Dart 和 JavaScript 一样,是单线程的,这意味着它根本不允许抢占。相反,线程显式屈服(使用async/await、FuturesStreams)。这使开发人员可以更好地控制执行。单线程可帮助开发人员确保关键功能(包括动画和过渡)执行完成,而无需抢占。这不仅对于用户界面,而且对于其他客户端-服务器代码,通常都是一个很大的优势。

当然,如果开发人员忘记让出控制权,这可能会延迟其他代码的执行。但是,我们发现忘记让步通常比忘记锁定更容易找到和修复(因为很难找到竞争条件)。

分配和垃圾收集

卡顿的另一个严重原因是垃圾收集。实际上,这只是访问共享资源(内存)的一种特殊情况,在许多语言中这需要使用锁。但是锁可能会在收集空闲内存时阻止整个应用程序运行。然而,Dart 几乎可以在*没有锁*的情况下执行垃圾回收。

Dart 使用先进的分代垃圾收集和分配方案,这对于分配许多短期对象特别快(非常适合像 Flutter 这样为每一帧重建不可变视图树的反应式用户界面)。Dart 可以分配一个带有单个指针碰撞的对象(不需要锁)。再一次,这导致平滑的滚动和动画,没有卡顿。

统一布局

Dart 的另一个好处是 Flutter 不会在您的程序和其他模板或布局语言(如 JSX 或 XML)之间拆分布局,也不需要单独的可视化布局工具。这是一个简单的 Flutter 视图,用 Dart 编写:

new Center(child:
  new Column(children: [
    new Text('Hello, World!'),
    new Icon(Icons.star, color: Colors.green),
  ])
)

image-20220424114156957

Dart 中的视图及其产生的结果

请注意可视化此代码产生的输出是多么容易(即使您没有使用 Dart 的经验)。

请注意,现在 Flutter 使用了 Dart 2,布局变得更加简单和清晰,因为new关键字是可选的,所以静态布局看起来更像是用声明式布局语言编写的,如下所示:

Center(child:
  Column(children: [
    Text('Hello, World!'),
    Icon(Icons.star, color: Colors.green),
  ])
)

但是,我知道您可能在想什么——*缺乏*专门的布局语言怎么能被称为*优势*呢?但它实际上是一个游戏规则改变者。这是一位开发人员在一篇题为“为什么原生应用程序开发人员应该认真看待 Flutter ”的文章中所写的内容。

在 Flutter 中,布局仅使用 Dart 代码定义。没有 XML / 模板语言。也没有视觉设计师/故事板工具。

我的预感是,听到这个,你们中的一些人甚至可能会有点畏缩。从表面上看,这也是我的反应。使用可视化工具进行布局不是更容易吗?在代码中编写各种约束逻辑不会使事情变得过于复杂吗?

对我来说答案是否定的。还有男孩!真是大开眼界。

答案的第一部分是上面提到的热重载。

我怎么强调这比 Android 的 Instant Run 或任何类似的解决方案早了好几年。它可以正常工作,即使在大型非平凡应用程序上也是如此。而且速度很快。这就是 Dart 对你的力量。

实际上,这使得可视化编辑器界面变得多余。我一点也没有错过 XCode 相当不错的自动布局

Dart 创建简洁易懂的布局,而“疯狂的快速”热重载让您立即看到结果。这包括布局的非静态部分。

因此,我在 Flutter (Dart) 中编写布局的效率要比 Android/XCode 高得多。一旦你掌握了它的窍门(对我来说这意味着几周),由于发生的上下文切换非常少,开销就会大幅减少。不必切换到设计模式,然后选择鼠标并开始四处点击。然后想知道是否必须以编程方式完成某些事情,如何实现等等。一切都是程序化的。API 设计得非常好。它很快就会变得直观,并且比自动布局/布局 XML 提供的结构更强大。

例如,这是一个简单的列表布局,它在每个其他项目之间添加一个分隔线(水平线),以编程方式定义:

return new ListView.builder(itemBuilder: (context, i) {
  if (i.isOdd) return new Divider(); 
  // rest of function
});

在 Flutter 中,无论是静态布局还是程序化布局,所有布局都存在于一处。新的 Dart 工具,包括 Flutter Inspector和大纲视图(利用所有布局都在一个地方)使复杂、漂亮的布局变得更加容易。

Dart 是专有语言吗?

不,Dart(像 Flutter 一样)是完全开源的,具有干净的许可证,也是ECMA 标准。Dart 在 Google 内外都很流行。在 Google 内部,它是增长最快的语言之一,被 Adwords、Flutter、Fuchsia等使用;在外部,Dart 存储库有 100 多个外部提交者。

Dart 开放性的一个更好的指标是 Google 以外的社区的增长。例如,我们看到来自第三方的关于 Dart(包括 Flutter 和 AngularDart)的文章和视频源源不断,我在本文中引用了其中的一些。

除了 Dart 本身的外部提交者之外,公共 Dart 包存储库中还有 3000 多个包,包括用于 Firebase、Redux、RxDart、国际化、加密、数据库、路由、集合等的库。

Dart 程序员会很容易找到吗?

如果知道 Dart 的程序员不多,会不会更难找到合格的程序员?具有讽刺意味的是,Dart 让寻找程序员*变得更加容易,因为它是一种学习速度非常快的语言。*已经了解 Java、JavaScript、Kotlin、C# 或 Swift 等语言的程序员几乎可以立即开始使用 Dart 进行编程。最重要的是,热重载鼓励用户玩 Dart 并尝试新事物,这使得学习 Dart 更快、更愉快。

以下是一位程序员在一篇题为“为什么 Flutter 会在 2018 年起飞”的文章中这样说的:

Dart是一种用于开发 Flutter 应用程序的语言,它很笨——学起来很简单。例如,谷歌在创建简单、有据可查的语言(如 Go)方面拥有丰富的经验。到目前为止,对我来说,Dart 让我想起了 Ruby,学习起来很愉快。它不仅适用于移动设备,也适用于网络

来自另一篇关于 Flutter 和 Dart 的文章,标题为“为什么选择 Flutter?而不是框架X?或者更好的是为什么我要全力以赴。

Flutter 也使用了由 google 创建的 Dart 语言,老实说我不喜欢 C# 或 JAVA 等强类型语言,但我不知道为什么 Dart 编写代码的方式看起来不同。我觉得写它很舒服。也许是因为它很容易学习,也很直接。

通过广泛的 UX 研究和测试,Dart 被专门设计为熟悉且易于学习。例如,在 2017 年上半年,Flutter 团队与八位开发人员进行了 UX 研究。我们向他们简要介绍了 Flutter,然后让他们放松了一个小时左右,创建了一个简单的视图。所有参与者都能够立即开始编程,即使他们以前从未使用过 Dart。他们专注于编写反应式视图,而不是语言。飞镖刚刚工作

最后,一位参与者(在任务中取得了特别大的进步)没有提到任何关于语言的内容,所以我们问他们是否意识到他们正在使用什么语言。他们不知道。语言*无关紧要*;他们在几分钟内就可以在 Dart 中编程。

学习一个新系统的困难部分通常不是学习语言,而是学习所有的库、框架、工具、模式和编写好的代码的最佳实践。而且 Dart 库和工具非常好并且有据可查。一篇文章宣称,“作为奖励,他们还非常关心他们的代码库,并且他们拥有我见过的最好的文档。” 学习 Dart 所花的一点点努力很容易被学习其余部分所节省的时间所弥补。

作为直接证据,谷歌内部的一个大型项目希望将他们的移动应用程序移植到 iOS。他们打算聘请一些 iOS 程序员,但决定尝试 Flutter。他们监控了让开发人员熟悉 Flutter 所需的时间。他们的结果表明,程序员可以在三周内学习 Dart*和*Flutter 并提高工作效率。相比之下,他们之前观察到让程序员仅在 Android 上加快速度的五周(更不用说他们必须为 iOS 招聘和培训开发人员)。

最后,文章“我们为什么选择 Flutter 以及它如何使我们的公司变得更好”来自一家将其大型企业应用程序在所有三个平台(iOS、Android 和 Web)上都迁移到 Dart 的公司。他们的结论:

更容易雇用。我们现在希望选择最好的候选人,无论他们来自 Web、iOS 还是 Android。

由于我们所有的团队都整合在一个代码库上,我们现在拥有 3 倍的带宽。

知识共享空前高涨

通过使用 Dart 和 Flutter,他们能够将*生产力提高三倍。*考虑到他们之前的所作所为,这应该不足为奇。与许多公司一样,他们使用*不同的语言、工具和程序员*为每个平台(Web、iOS 和 Android)构建单独的应用程序。切换到 Dart 意味着他们不再需要雇佣三种不同类型的程序员。他们很容易将现有的程序员转移到 Dart。

他们和其他人发现,一旦程序员开始使用 Flutter,他们往往会爱上 Dart。他们喜欢语言的简洁,以及缺乏仪式感。他们喜欢级联、命名参数、异步/等待和流等语言特性。最重要的是,他们喜欢由 Dart 实现的 Flutter 功能(如热重载),以及 Dart 帮助他们构建的美观、高性能的应用程序。

flutter 2

随着本文的发布,Dart 2也正在发布。Dart 2 专注于改善构建客户端应用程序的体验,包括开发人员速度、改进的开发人员工具和类型安全。例如,Dart 2 具有健全的类型系统类型推断功能。

Dart 2 还使new关键字可选。这意味着可以*在完全不使用任何关键字的情况下*描述许多 Flutter 视图,从而使它们不那么混乱且更易于阅读。例如:

Widget build(BuildContext context) =>
  Center(child:
    Column(children: [
      Text('Hello, World!'),
      Icon(Icons.star, color: Colors.green),
    ])
  )

Dart 2 还使用类型推断来使const关键字的许多使用可选,因为不需要constconst上下文中冗余指定。例如,声明:

const breakfast = {
   const Doughnuts(): const [const Cruller(), const BostonCream()], 
};

现在可以替换为:

const breakfast = {
   Doughnuts(): [Cruller(), BostonCream()],
};

因为breakfastis const,所以推断其他所有内容也是const如此。

秘诀就是专注

Dart 2 中的改进集中在优化客户端开发。但 Dart 仍然是构建服务器端、桌面、嵌入式系统或其他程序的好语言。

专注是*好事*。几乎所有经久不衰的流行语言都受益于非常专注。例如:

  • C 是一种用于编写操作系统和编译器的系统编程语言。它变得如此之多。
  • Java 是为嵌入式系统设计的语言。
  • JavaScript 是一种用于 Web 浏览器的脚本语言 (!)。
  • 即使是备受诟病的 PHP 也取得了成功,因为它专注于编写个人主页(它的名字来源于此)。

另一方面,许多语言已经明确尝试(但失败了)完全通用,例如 PL/1 和 Ada 等。最常见的问题是,*没有焦点*,这些语言就成了众所周知的厨房水槽。

许多使 Dart 成为出色客户端语言的特性也使其成为更好的服务器端语言。例如,Dart 避免了抢占式多任务处理这一事实使其具有与服务器上的 Node 相同的优势,但打字更好、更安全。

为嵌入式系统编写软件也是如此。Dart 可靠地处理多个并发输入的能力是这里的关键。

最后,Dart 在客户端的成功将不可避免地引起更多在服务器上使用它的兴趣——就像 JavaScript 和 Node.js 所发生的那样。为什么要强迫人们使用两种不同的语言来构建客户端-服务器软件?

结论

对于 Dart 来说,这是一个激动人心的时刻。使用 Dart 的人喜欢它,Dart 2 中的新功能使它成为你工具库中更有价值的补充。如果您还没有使用过 Dart,我希望这篇文章为您提供了有关 Dart 的新功能或不同之处的宝贵信息,并且您会尝试一下它——以及 Flutter。