移动研发由来已久,从早期的 J2ME 开始,到后来居上的 Windows Mobile 、 Symbian ,小众却各领风骚的 BREW 、 Palm 、 BlackBerry ,以及当前几乎平分市场的 Android 和 iOS 。面对如此多的平台和技术,开发者可以择其优者而习之,公司却不得不兼而用之,但随之而来的必然是成本的上升。
因此,跨平台的呼声一直很高,各种方案也不断面世: PhoneGap( 即 Cordova) 、 React Native 、 WEEX 以及最近的 Flutter 。 Flutter 无疑是其中的佼佼者,一经问世就已引起广泛关注,无论是其革新的理念,还是其“正统的出身”(毕竟 Flutter 与当今两大移动平台之一 Android 师出同门)。
在有多种选择时总要进行一番对比,孰优孰劣众说纷纭,抱着小马过河的心态,各大公司也不乏实践,做出一些有益的尝试。接到本书的翻译任务时,正值译者将 Flutter 技术尝试运用于公司的 ToB 项目之刻。
《 Flutter实战 》 既然名为实战,内容紧扣主题,不会涉及太多概念介绍、原理深究,一切以实用为目的,是快速入门的好帮手。作者Frank Zammetti 深耕移动研发多年、亲历跨平台技术发展,并且撰写了多部相关技术书籍,是一位当仁不让的资深技术专家。值得一提的是,Frank Zammetti 言语诙谐、娓娓道来,令读者在阅读时宛如与真人对话一般,确实是一种不可多得的阅读体验。
望读者在阅读的同时,亲自实践 ( 下载源码并在模拟器或手机上运行 ) 一番以加深理解。
节选自 《 Flutter实战 》一书
-------------------------------------图书基本信息----------------------------------------------------
书名: 《 Flutter实战 》
ISBN:9787302556084
定价:79.8
出版时间:2020年7月
京东移动端链接: https://item.m.jd.com/product/ 12900254 .html
内容简介:
《 Flutter实战 》 围绕Flutter的跨平台能力及其来龙去脉展开讲解。当今,移动开发领域发展迅速,Flutter是由Google公司推出的开源移动应用开发SDK,使用Flutter可以为Android、iOS和Google Fuchsia系统开发应用。
通过学习本书,你将掌握三个可直接安装在自己的移动设备上并实际体验的应用(个人信息管理、聊天系统和游戏)。你将扎实掌握Flutter基础知识,并由此快速构建两个常见应用,你还将通过学习构建游戏项目,从完全不同的角度了解Flutter的强大功能。通过构建这三个应用,你将了解现实世界中发生的问题,并通过相关提示及技巧掌握解决办法,所有这些都将使你的Flutter学习高效且充满乐趣!
本书将使你掌握使用Flutter构建应用的能力,进而可以自行构建更高级的应用。完成本书的学习后,你将站在更高的起点并且能够做好充分准备,在Flutter开发领域从容应对任何挑战。本书是学习Flutter不可错过的经典作品。
----------------------------------------样章试读--------------------------------------------------------
第1 章
█ █ █
初识Flutter
欢迎来到起跑线!
如果向十个移动开发者问起:他们是如何在Android 和iOS 设备上开发移动应用
的?你很可能得到十种不同的回复。不过由于新技术Flutter 的出现,这样的情形可能
不会持续太久。
在本章中,我们将聚焦移动开发,讲解Flutter 在移动开发方面扮演的角色,及其
在某些方面是如何彻底改变移动开发的。我们将初步理解Flutter 的方方面面,为在本
书随后的部分构建一些真正的应用做好准备。
好了,让我们这就开始吧,下面首先介绍一点儿有关移动开发的内容。
1.1 在深渊中沉思
软件开发无易事!
对有些人来说,编程就是家常便饭,我也是其中一员。我不是要炫耀我的个人经
历而让你觉得无聊,而事实是从7 岁那年起,我就已经开始使用各种方式进行编程了。
也就是说,我从事这个行业已经快40 年了(其中有25 年从事专职软件开发)。经历了
这么多,我领悟到一个真谛,正如开篇所说:软件开发无易事。不可否认,有些独立
的任务和项目可以轻松搞定;但总的来说,我们程序员从事的是一项从根本上就相当
棘手的工作。
而且我还没提及移动开发呢,它可能更难!
大约二十年前,我开始从事移动开发工作。那时还是Microsoft 公司的Windows
CE/PocketPC 系统和Palm 公司的PalmPilot 系列设备及其PalmOS 系统(尽管还有一些
其他的设备和操作系统,但可以说,在一定程度上它们是当时的主流)的天下。就这一
点来说,移动开发还不是太糟糕:只需要关注有限的几种设备及功能,并且在开发工
具方面也并不缺乏。虽然这些工具不像今天我们所使用的那样优秀,但基本上只有一
种用于开发PocketPC 或PalmOS 应用的方式,而不是有一大堆的选择。这听上去糟透
了,并且在某些方面的确十分糟糕,但选择减少的同时也省去了开发者的困扰,而选
择的多寡正是当今软件工程领域最大的争议之一。
另外,当时完全没有人意识到要进行跨平台开发。如果需要在两个平台上运行,
只能各自编写一套代码,而这在今天看来是多么不可思议。不过在当时,只需要考虑
两个平台的差异性,编写两次代码也就不那么令人难以接受了。你很容易就能找出一
个应用在不同平台上的特性,因为开发者不可能或无法将其从一个平台迁移到另一个
平台(事实上,最大的理由是,这样做并不值得付出相应的时间和精力)。
从那时起,移动开发领域经历了诸多革新,见证了诸多改变、扩张与收缩。一段
时间以来,我们需要支持诸多平台:Android、iOS、webOS、Tizen、Windows Mobile,
也许还有其他一些我未记住的平台。在那个年代,在各平台间迁移应用仍然十分普遍,
因为并没有一种好用的跨平台解决方案,至少没有一种不做出重大妥协。是的,随着
时间的推移,迁移变得更友好,因为至少原生开发工具有了长足改进,尽管你不得不
为同一个应用编写多次代码,但是每次编写都较过去容易不少。Apple 公司于2008 年
发布了iOS SDK,随后Google 也于一年后发布了Android SDK,而为这两种平台开发
应用意味着(直到今天仍然是)使用两种SDK,因为iOS 开发使用的是Objective-C 语言
(现在Swift 也用得越来越多了),而Android 开发主要使用Java 语言。
最终,平台的数量开始逐渐减少,因为在这一领域的胜败早已尘埃落定。今天的
赛道基本上是Android 和iOS 双雄争霸(尽管还有一些其他的平台,但此刻它们的影响
力几乎可以忽略不计,并且多数开发者,在我看来甚至可以说是绝大多数开发者,都
倾向于无视它们,除非有特别的目的必须包含它们)。于是,真正的跨平台开发理念开
始变得更加吸引人和可行了。
互联网的兴起提供了一种选择,因为你可以基于Web 技术开发并打包成一个移动
应用,使它在Android 和iOS(甚至更多平台)上的表现几乎一致。但我们需要为此做出
一些妥协,而这些妥协尽管随时间的推移减少了,但却一直存在。比如性能和真正的
原生能力,对Web 技术来说仍然有些棘手。
除了Web 技术,在过去几年中,我们还见证了数种跨平台开发技术和工具的诞生,
使用这些技术和工具,只需要编写应用一次,就可以运行于所有设备,在保持一致性
的同时获得原生般的体验。其中广为人知的有Corana SDK(主要用于游戏,尽管并非
唯一选择)、Xamarin、PhoneGap(本质上仍然是Web 技术,但巧妙地使用一个原生的
WebView 组件进行了包装)、Titanium、Sencha Touch(虽然还是基于Web 技术,但提供
了较好的抽象)等,其他就不一一列出了。当下并不缺少选择,并且它们也各有拥护者
和反对者。
自从Flutter 以全新竞争者的身份进入该领域后,便远胜其他一切技术,成为编写
跨平台移动应用的唯一正确方式。
1.2 透过名字这一表象
众所周知,Flutter 是由Google 公司发明的,这是一家对互联网发展有着重要影响
力的公司。在2015 年的Dart 开发者峰会上(请记住Dart,因为我们很快就会回顾与之
相关的内容),Flutter 以Sky 为名诞生了。最初,Flutter 只是运行在Google 自家的Android
操作系统上,但不久后就被迁移到Apple 的iOS 操作系统上,而这两种系统正是引领
当今移动操作系统的佼佼者。
自首次亮相后,Flutter 陆续发布了多个预览版本,最终于2018 年12 月4 日发布
了第一个稳定版本:Flutter 1.0。这标志着Flutter 迎来大爆发,对开发者来说是时候转
向Flutter 阵营了——事实上他们已经这么做了。由于一些非常正确的决策,Flutter 的
流行可以说是如流星般迅速。其中一个是:Flutter 最初的目标,或者说主要目标之一,
就是无论如何都要使应用的UI 渲染速度达到流畅的120fps。Google 的工程师们非常
明白流畅的UI 正是用户喜闻乐见并且想要拥有的体验,因此在设计Flutter 时它被列
为所要考虑的首要特性之一。这是明确要实现的最高目标,到目前为止还没有一个跨
平台的移动框架可以达成这一点(即使那些并非跨平台的移动框架也曾为此付出过巨
大努力)。
Google 在设计Flutter 时做了一系列不同于其他移动开发方案的关键决策,其中之
一是Flutter 自主渲染其UI 组件。与其他框架不同,Flutter 并未使用原生平台的组件。
换句话说,当你告诉Flutter 显示一个按钮时,Flutter 将自主完成该按钮的渲染;而非
转嫁给底层OS 来渲染该按钮,这也是其他大多数框架的做法。这一点展示了Flutter
与其他几乎所有的跨平台方案的重要区别,也正因此而保证了Flutter 在不同平台上的
流畅性。受益于此,新的UI 组件、微件(也请记住这个概念,它即将像Dart 一样隆重
登场)可以被快速且简单地添加至Flutter,而无须担心底层平台是否支持。
自主渲染的特性使得Flutter 可提供特定风格的微件。Flutter 支持两种风格的微件:
Material 风格的微件和Cupertino 风格的微件。前者实现了Google 自家的Material 设计
语言,也就是Android 默认的设计语言;后者实现了Apple 的iOS 设计语言。
Flutter 内部可从概念上划分为4 个主要部分。首先是Dart 平台,因内容庞杂,需
要用一整章来介绍,我将暂时跳过它并在下一章中专门讲述。
接下来我们来看第二个组成部分:Flutter 引擎。这是一个(绝大部分)由C++语言
实现的代码库,其核心性能接近原生级别,同时使用Skia 图形引擎来完成渲染工作。
Skia 是一个轻量级的开源图形库,也是由C++实现的,经改进后在所有兼容平台上都
可获得极其优秀的性能。
作为第三个主要组成部分,Flutter 提供了一套所谓的基础类库——用于封装两个
平台的原生SDK 的接口。其目的在于消除原生平台API 的差异,以便支持Flutter API
的一致性。也就是说,你无须关注如何在iOS 及Android 上启动拍照应用,也不必费
心调用两个平台特定的API。你只需要了解调用哪个Flutter API 来启动拍照应用,它
将自动适配两个平台。
最后一个组成部分是微件,但正如Dart 一样(内容庞杂),也须另起一章专门介绍,
我们稍后再讨论它们。
简而言之,以上内容就是Flutter 的全部。老实说,以上所述并未覆盖多少开发
Flutter 应用所需掌握的知识点。尽管如此,我仍然认为了解一点发展历史以及所使用
开发工具的原理,总是有用的。希望你也有同感!
接下来将辨析几个我刚刚提出的概念,对这些概念稍作扩展并究其细节,下面就
从Dart 开始吧!
1.3 Dart:众神的语言
当Google 启动Flutter 项目时,有一个早期决策需要做出:应该使用何种编程语
言?或许可以选择基于网页的语言(比如JavaScript),或许因Android 使用Java 的缘故
而自然而然地选择Java。或许,既然他们打算支持iOS,Objective-C 或Swift 也是不
错的选择(毕竟Swift 表面上是开源的,因此我敢打赌,Flutter 团队内部早期一定就此
有过争论)。或许,Golang、Ruby 之类的语言更好。传统的C/C++怎么样?还是响应
Microsoft 的号召加入C#阵营(因为C#也是开源的)?
我相信有太多的选择,但最终Google 决定(由于诸多因素)使用了一种多年前他们
自己开发的语言:Dart。
由于接下来的整个第2 章都用来介绍Dart,因而在这里我将避免涉及太多细节,
但浏览一下下面这个简单的示例还是有必要的:
import "dart:math" as math;
class Point {
final num x, y;
Point(this.x, this.y);
Point.origin() : x = 0, y = 0;
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return math.sqrt(dx * dx + dy * dy);
}
Point operator +(Point other) => Point(x + other.x, y + other.y);
}
void main() {
var p1 = Point(10, 10);
var p2 = Point.origin();
var distance = p1.distanceTo(p2);
print(distance);
}
是否理解每一行代码并不重要。事实上,此刻你无须理解任意一行代码。如果有
过使用Java 或类C 语言的经验,我相信你能理解它们且没什么太大障碍,这正是Dart
的一大优势:其语言特性与绝大多数现代开发者所熟悉的那样如出一辙,可以被快速
且容易地掌握。
■ 注意:
有趣的是,C语言本身以及其他类C语言都是从ALGOL这种相当古老的语言发展
而来的。
在此,我没有过多地深入探讨Dart 的本质细节(第2 章要介绍的主要内容),只是
想适当地介绍一点Dart 的背景。如前所述,Google 于2011 年创建了Dart,首次公布
于在Aarhus Denmark 举行的GOTO 会议上。Dart 的首个版本1.0 发布于2013 年11 月,
在其发布后的大约两年后Flutter 发布了。顺便说一句,我们都应感谢Dart 的创建者
Lars Bak(他同时也是支撑Chrome 和Node.js 的V8 JavaScript 引擎的开发者)和Kasper
Lund。
Dart 语言小而美,由于对Flutter 的重要性,它很快便获得大量的关注。尽管作为
一种通用目的的编程语言,Dart 能够且已被用于构建各类应用,从Web 应用到服务器
端,再到IoT(物联网)应用以及其他领域。大概在我撰写本章内容时,JAXenter 公开了
一项关于2019 年对开发者最重要的编程语言的调查(详见https://jaxenter.com/poll-resultsdart-
word-2019-154779.html),其结论指出有两种语言脱颖而出且远远超出其他语言,
它们是Dart 和Python,并且Dart 全面胜出。该项调查还显示Dart 在2018 年经历了蓬
勃发展,且Flutter 几乎可以说是其唯一强大的推动者,光靠Dart 自己可能还不足以解
释这些结论,但公平地讲,Dart 正迅猛进击所有领域!
因此,Dart 正毋庸置疑地受到越来越多的关注。
Dart 涵盖了哪些内容?简要来讲,有几个关键的要素,并且在前面的代码示例中
已演示了大部分:
● Dart 是完全面向对象的。
● Dart 是一种垃圾回收型语言,因此无须关注内存的分配与回收。
● 语法风格基于C 语言,易于为大部分开发者所掌握(诚然,Dart 确实有一些特
性可能会难住你,不过不会比其他有着类似语法的语言更糟)。
● 支持通用语言特性,如接口、混入(mixin)、抽象类、泛型具体化(相对于类型
擦除而言)以及静态类型检查。
● 拥有完善的输入辅助系统(但在相同类型的输入上的灵活性,使得Dart 对开发
人员而言起到真正的辅助作用而非负担)。
● 并发隔离,因此你将获得独立的线程,它们以消息传递而非内存共享的方式
通信。虽然这会损失一些性能,但能够降低其他形式的并发编程模型带来的
风险。
● Dart 可以AOT(ahead-of-time)方式编译为原生代码以达到汇编级别的最优性
能。Dart 代码可被编译为ARM 或x86 架构下的二进制代码,但同样也可被编
译为JavaScript,因此Dart 代码甚至还能在网页上勉强运行。暂且抛开这种
转译,既然Flutter 以移动平台为目标,那么Dart 代码将以AOT 为主要编译
方式。
● Dart 在其语言基础之上提供了一个相当庞大的包的仓库,其中包括几乎大部
分开发者都会用到的各种附加功能,便于他们轻松地导入自己的项目。
● 支持众多流行的开发者工具,包括Visual Studio Code 和IntelliJ IDEA。
● 将快照作为Dart 虚拟机的核心部分,从而提供了一种在运行时存储或序列化
对象及数据的方式(Dart 程序可被编译为快照,其中包含了所有的程序源代码
和经过预分析的依赖,并且可在启动时立即执行,同时快照还被重度应用于
并发编程)。
Dart 现在已成为ECMA 的标准之一,且由技术委员会TC52 管理,可以在网站
www.dartlang.org/guides/language/spec 上查阅最新的规范说明(更常见的做法是访问
Dart 语言的主页www.dartlang.org)。
前面已提到,接下来的第2 章将为你提供关于Dart 的最新信息,且足以应对即将
到来的Flutter 代码。但以上介绍的相关内容,仅限于作为一种适当且足够的补充简介。
1.4 拥抱微件
让我们回过头来说一说整场演出中真正的明星——Flutter,以及一个比其他任何概
念都更为重要的、用以支撑Flutter 一切的概念——微件。
在Flutter 的世界里,任何事物都是微件。当我提到任何事物时,意指几乎所有的
事物都是微件(想在Flutter 中找出一个不是微件的事物,难度要比找出是微件的所有事
物高得多)。
那么,什么是微件?它们是应用的UI 组成部分(尽管并非所有的微件都会显示在
屏幕上,只有小部分会显示)。显然,微件也可以是代码行,如下所示:
Text("Hello!")
以下代码清单同样是微件:
RaisedButton(
onPress : function() {
// Do something.
},
child : Text("Click me!")
)
以下代码清单也是微件:
ListView.builder(
itemCount : cars.length,
itemBuilder : (inContext, inNum) {
return new CarDescriptionCard(card[inNum]);
}
)
最后,以下代码清单还是微件:
Center(
child : Container(
child : Row(
Text("Child 1"),
Text("Child 2"),
RaisedButton(
onPress : function() {
// Do something.
},
child : Text("Click me")
)
)
)
)
最后这个微件很有趣,它实际上使用了一种由多个微件构成的层级结构:一个
Center 微件,里面嵌入了一个Container 微件,而在这个Container 微件的内部又嵌入
了一个Row 微件,在这个Row 微件的内部又嵌入了两个Text 微件以及一个
RaisedButton 微件。它们是什么微件并不重要(尽管命名已给出含义),重要的是你所看
到的整个微件的层级结构,正是微件在Flutter 领域内的存在形式。
没错,在Flutter 中微件无处不在。微件就在我们身边。Flutter 本身就是一个强大
的UI 框架:你得到的是一个微件!
正如开头提到的,Flutter 中几乎所有事物都是微件。当你在用户界面的上下文环
境中提到微件这个词时,人们所能想到的各种显而易见的事物有按钮、列表、图片、
文本输入框等事物。它们当然是微件。但是,在Flutter 中那些一般你不认为是微件的
事物,仍然是微件,比如图片的外边距、文本输入框的状态、显示在屏幕上的文本甚
至应用所使用的主题,所有这些事物也都是微件。
既然Flutter 中的一切都是微件,一个显然的结论就是:你的代码从本质上看就是
由众多微件堆叠而成的(这在Flutter 中有一个特别的叫法:微件树)。可见,大多数微
件都是容器,意味着它们可以嵌入其他微件。其中一些只能嵌入一个,另外一些可以
嵌入多个。同样,嵌入的微件又可以分别嵌入一个或多个其他微件,以此类推。这就
是微件的存在形式。
所有的微件都是Dart 类,所有微件只有一个明确的要求:它们必须实现build()
方法。这个方法必须返回其他微件!关于这一点少有例外,一些低级别的微件(如Text
微件)会返回一个原生类型(此处为String),但大多数都返回一个或多个其他微件。除
此之外,从代码级别看,一个微件就是一个普通的Dart 类(除了第2 章将要展示的少
量语法之外,这与你在其他面向对象编程语言中认识的类没什么两样)。
Flutter 微件扩展自Flutter 所提供的几个少数标准类之一,符合典型的面向对象编
程范式。扩展类决定了我们将使用何种基本微件,而在99%的时间里你将围绕两种微
件进行开发:StatelessWidget 和StatefulWidget。
继承自StatelessWidget 的微件不会发生改变,它们因不含状态而被称为无状态微
件。比如显示小图的Icon 微件,显示字符串文本的Text 微件,就属于此类微件。无
状态微件的示例如下:
class MyTextWidget extends StatelessWidget {
Widget build(inContext) {
return new Text("Hello!");
}
}
是的,就是这么简单!
相对而言,StatefulWidget 基类在内部带有一个状态属性,当用户与之交互时将以
某种形式发生改变。如CheckBox、Slider、TextField(当你遇到此类将首字母大写的单
词写法时,意即真实的Flutter 微件类名,而普通的表达将正常译出,如文本表单域),
都是大家熟知的有状态微件。当你编写这样一个微件时,实际上须创建两个类:有状
态微件类和相应的状态类。下面展示了一个有状态微件及其关联的状态类:
class LikesWidget extends StatefulWidget {
@override
LikesWidgetState createState() => LikesWidgetState();
}
class LikesWidgetState extends State
int likeCount = 0;
void like() {
setState(() {
likeCount += 1;
});
}
@override
Widget build(BuildContext inContext) {
return Row(
children : [
RaisedButton(
onPressed : like,
child : Text('$likeCount')
)
]
);
}
}
像之前一样,无须纠结太多细节,目前并不期望你能读懂这些代码,我们将在第
2 章中深入讨论,在那之前你只需要掌握Dart 的一些基础概念即可。但同时,我相信
你多少对我们所讨论的内容有一个基本的认知,因为它们还是比较浅显直白的。除了
微件代码及其状态对象是如何交互和发生联系的部分有些难懂之外,其他大部分内容
都还算简单,不过不必担心,这样的状况不会持续太久!
暂时回到无状态微件的概念上来,我们应注意到术语“无状态”其实有些不够准
确,因为Dart 中的类封装了属性和数据,无状态微件也不例外,因此从某种意义上说,
它也拥有状态。有状态微件与无状态微件的关键不同在于,无状态微件在“状态”发
生改变时并不会被Flutter 核心框架触发而自动重新渲染,而有状态微件则会自动触发
重新渲染。当有状态微件的状态改变时,无论因何缘故导致,都将触发某些特定的生
命周期事件。这些触发生命周期事件回调的函数调用,最终引起Flutter 重新渲染微件
在屏幕上占据的部分(如果这次改变是必需的,Flutter 将替你做出决策,因为它清楚这
个微件在事件发生前后的状态)。
我们可以这样理解:两种类型的微件都有状态,不过Flutter 只关注有状态微件的
状态,甚至在某种程度上会管理其状态。因此,只有有状态微件才会在适当的时候自
动重新渲染,且受控于Flutter 框架本身而不是你的代码。
你可能倾向于认为你会一直使用有状态微件,这样就可以省很多事儿,但从后续
我们构建的应用中你将看到,事情并非如此。事实是:你会发现你将不可思议地大量
使用无状态微件。我们改天再来讨论这个话题。
你应该已经注意到有两件事的重要性非同一般。其一,微件构成了Flutter 的UI。
并形成了早些时候我曾提到的微件树。同时,微件的代码是面向对象的,UI 的构建方
式就是一种组合范例。这一点非常重要,大部分Flutter 微件本身都相当简单,并且也
只能通过组合方式来构建强大的UI。有些框架使用可以称为“全能”的组件,它们自
身就代表整个应用。Flutter 则完全不同,即使相对较小的UI 都很可能由多个微件组合
而成。
其二,Flutter 通过代码构建UI。我知道这是显而易见的,但请想一想:Flutter 不
像网页开发那样,并没有一种专门用于构建UI 的标记语言。由此带来的好处是只需
要学会一种语言,掌握一种编程范式。乍一看这没什么,但却是Flutter 与相对其他竞
争者的巨大优势。
暂时这就是所有你需要掌握的微件知识点了。我们将从第3 章开始将Flutter 自带
的微件列表整理为目录并深入介绍它们,然后在第4 章基于这些微件来构建三个应用,
以讲解每个微件的用法。最终你将对进行Flutter 开发时用到的大多数常见微件形成良
好的理解,并很好地掌握如何使用和构建微件。
1.5 言归正传:Flutter 的优劣对比
与其他任何框架一样,合格的开发者需要评价我们可能考虑的任意选择的收益和
风险,Flutter 也不例外。我个人认为Flutter 可以做很多事,但它并非灵丹妙药,它也
有自身的问题。Flutter 有其不足之处,它并不适用于所有项目,尽管如此,我仍然坚
信,即使它不是最好的,但也绝不是最差的。
既然如此,我们就来说一说Flutter 的优劣,为了容易理解,我们将它与其他可选
方案进行对比。
● 优势:热重载——当我们搭建好Flutter 开发环境并快速预览一个模板应用之
后,我们将回顾这一点,你将看到,这是Flutter 的一个巨大优势。在集成了
第三方的Expo 组件后,React Native 也可以拥有这一能力。但Flutter 的表现
更为优异,以我的经验来看,更为流畅。其他框架则很少能做到这一点。
● 劣势:专注移动应用——在撰写本书时,你只能使用Flutter 开发iOS 和Android
移动应用。有了Flutter 后,你会失望于它不能满足你的所有开发需求。不过,
你可以选择使用Flutter 进行Web 开发乃至Windows、Mac 和Linux 原生开发。
不久之后,这种劣势可能变成优势,因为整个计算机世界都可以使用Flutter
构建(对某些人来说,将成为一种意义重大的优势:Google 将比现在拥有更多
话语权——你必须自己做出决定)。
● 优势:完全跨平台——你的Flutter 应用将以最小代价运行于iOS 及Android(乃
至Android 的继任者Fuscia)平台上。与众不同的是,Flutter 提供了两组微件,
其中一组适用于iOS,而另一组则适用于Android。因此,你的应用不仅可以
表现一致,还可以与平台完美融合(这取决于你,而并非强制要求)。其他平台
则很少能像Flutter 这么轻松做到这一点(你通常需要编写大量分支代码以适配
两个平台,使用Flutter 则相对轻松很多)。
● 劣势:代码混合——一些开发者,特别是那些拥有Web 开发背景的,认为UI
与逻辑的分离是再普通不过的做法(相对于使用HTML/CSS 来定义UI,使用
JavaScript 及运行于后台的语言定义逻辑),他们很可能对Flutter 代码将UI 与
逻辑混合在一起的做法感到惊讶。当然并非只有Flutter 这么做,React Native
也曾因此饱受诟病。
● 优势:Dart——Dart 简洁而强大、面向对象、强类型,并赋予开发者快速且可
靠的代码高产能力。一旦掌握,大多数开发者对Dart 的热爱都会高过
JavaScript、Objective-C 和Java。
● 劣势:Google——我把这点列为劣势,更多是个人主观判断,你当然可以同意
或反对(老实说,我自己也一直踌躇不定)。有些人对Google 对互联网的掌控
力之大而有所不满,即使那些掌控并非Google 有意而为之。当你作为游戏的
主宰者时,你会倾向于拥有绝对的控制力。尽管如此,有人认为使用Google
独自研发的技术开发(所有平台的)移动应用有些不自量力。当然也会有人为有
如此强大的后盾支持而拍手称快。所以,这是一场你必须自己去寻找答案的
辩论。
● 优势:微件——Flutter 自带一组丰富的微件,并且实际上将满足你构建任何应
用之需。尽管如此,你仍然可以创建自己的微件(实际上,你必须创建自己的
微件,但它们的复杂度将大有不同),你甚至可以引入其他第三方微件以扩展
Flutter 的能力。新创建或引入的微件与Flutter 自带的微件一样容易使用。以
React Native 为例,其主要依赖添加诸多第三方微件来弥补自有微件的不足,
而Flutter 则提供了太多开箱即用的微件。
● 劣势:微件树——这点被归为劣势是由于有时你最终会因嵌套太深(如果没有
其他原因的话),导致代码可读性太差,而难以理解其结构。在过去的二十年
中,随着万维网的兴起,我们已经变得对此有些习以为常了,因为与HTML
遇到的问题如出一辙。但因为事实上Flutter 中的任何事物都是微件,所以嵌
套有时比HTML 还要深,并且Dart 代码风格导致阅读此类代码有些棘手。当
然,你可以运用一些技术手段来减少嵌套,我将在随后带领大家阅读真实的
应用代码时就此再作讨论,但嵌套过多的劣势仍需要我们时刻注意,并自行
处理。关于这一点,Dart 和Flutter 都无能为力。
● 优势:开发工具——在1.6 节中,你将看到完成Flutter 基础开发环境的搭建是
如此简单。尽管如此,你可以抛开基础开发环境,而是使用那些你在进行其
他开发工作时所习惯使用的工具。这一点再次减少了开发者的阻力。
● 劣势:响应式编程及状态管理——Flutter 通常被认为是一种响应式编程范式,
这意味着可通过给定微件的当前状态来管理应用的UI。前面介绍过的build()
方法以当前状态作为入参,并返回包含当前状态的微件的可视化描述。当状
态发生改变时,微件通过再次调用build()方法,同时传入其最新的状态进行重
建以“响应”该变化。这一切的发生都表现为Flutter 的函数及其生命周期;
你(通常)不必考虑太多,而只需要提供build()方法。相对于其他框架,你需要
先构造一个微件,再调用一系列赋值方法以设置其正确的状态。在Flutter 或
其他框架中,这一范式被广为接受,但它也可能被当作Flutter 的一个劣势,
因为有时候,在完成一些微小的功能时无可辩驳地更为复杂(我们将在后续章
节中见识到这种复杂性以及一些相应的处理方法)。与此相关的还有状态管理
话题,并没有一种标准可用来判断对错,至少在撰写本书时,这仍是Flutter
的一大不足。你会有多种方式用来管理状态,或优或劣,但你必须决定哪种
最适合自己(当然,我也会给出我认为不错的方式)。Google 正在拟订一种标准,
但目前还未完成,所以直到它完成之前,我将把(对状态管理)缺乏有力的指导
视为Flutter 的劣势之一(尽管有些观点认为这种灵活性可被当作优势来看,但
无论如何我都不会卷入任何无意义的争论之中)。
● 优势:平台特定的微件——由于Flutter UI 通过代码构建,维护一套代码的同
时支持iOS 和Android 就变得简单直接了,即使在需要处理一些平台差异时也
是如此。举例来说,你总是可以在运行时通过查询Platform.isAndroid 或
Platform.isIOS 的值来判断当前运行环境,并借此判断应该构造何种平台特定
的微件。也许你希望在Android 平台上使用RaisedButton 而在iOS 平台上使用
扁平的Button。没有必要维护两套不同的代码,多数情况下一个简单的分支
判断即可完成此类工作。
● 劣势:应用大小——因为需要嵌入核心Flutter 引擎、Flutter 框架、支持库以及
其他资源,Flutter 应用一般要比纯原生方式开发的应用大一些。一个简单的
“Hello, World!”Flutter 应用,大小可达7MB。这当然需要权衡,如果有一个
用例对应用大小有严格要求,那么Flutter 也许不是最佳选择。
好了,至此我敢说你已经掌握了,也可以说积累了有关Flutter 的一些基本概念,
你对Flutter 有了一个大致的了解。让我们开始动手写一些真正的代码吧!
1.6 无须多言,向Flutter 进发吧
在真正动手写代码之前,我们应该先安装Flutter 以及一些需要用到的开发工具,
对吧?“工欲善其事,必先利其器!”
幸运的是,Flutter 开发环境的搭建相当简单。
1.7 Flutter SDK
毫无疑问,首先要完成的就是下载、安装以及配置Flutter SDK。这是一切的基础。
接下来,虽然不是必需的,但为配合本书使用还是建议大家下载、安装和配置Android
Studio(包括Android SDK 和模拟器的设置)。
打开网址https://flutter.io,这里是Flutter 安装过程和文档的“一站式购物平台”。
单击顶部的Get Started 按钮,在Install 页面可以选择你所使用的操作系统(Windows、
macOS 或Linux)。
■ 注意:
我是一名忠实的Windows用户。Windows是我所知道的最好的操作系统。因此,
本书将围绕Windows来讲解。说到这儿,我将努力指出不同系统间的重大区别。总体
来说,无论你是否使用Windows,都不会有太大区别。虽然安装软件的过程的确有所
不同,但是Flutter网站会在你需要协助的地方给予正确的指引。
选择适合的链接,Flutter 网站将为你提供下载及安装SDK 的信息。与其他SDK
或软件不同,你可能会遇到一点困难。必须指出的一点是如何将SDK 添加至系统环
境变量的那部分说明。尽管这样做有利于编译构建,但你仍然可以跳过这一步骤,如
果愿意的话。不过要注意,如果坚持跳过,所有命令的执行都必须位于SDK 路径之
下,否则必须指定SDK 的完整路径。一旦我们完成Android Studio 的安装,你就会发
现无须添加SDK 至系统环境变量,只有当确实需要在命令行环境下执行SDK 命令而
非通过Android Studio 执行编译构建时,设置系统环境变量才有用。
说到命令,事实上你可能需要执行的一条命令,一条按照Flutter 网站上的安装说
明需要在SDK 安装完毕时执行的命令,就是flutter doctor。大多数命令将被转发给
SDK,除此之外就是flutter,这也是实际上执行的程序,doctor 是可以发送给它的命令
之一。这可能是最重要的一条命令,可用来执行一系列的检查和配置以确保Flutter 可
以运行。
刚开始执行这条命令时,你可能会看到一些可以预见的失败条目,下一步将解决
这个问题:安装Android Studio。
1.8 Android Studio
Flutter 网站上的说明将指导你安装Android Studio,虽然在不同系统上略有不同,
但是当你下载完成后,你将启动并执行Android Studio 的安装向导。你将下载Android
SDK 和模拟器镜像,以及其他一切必需的工具。同时,你需要安装一些与Dart 和Flutter
相关的特定插件。
现在,完成上述步骤后,如果继续遵照安装说明,接下来你将需要将Android 手
机或平板模拟器连接到计算机,并确保flutter doctor 指令运行无误。尽管如此,你仍
然可以跳过该步骤!当然,如果确实拥有一台Android 设备,你完全可以跳过它。
不过,如果你是一名iOS 爱好者,并且你不想在开发Flutter 代码时使用真实
设备——老实说,我也不喜欢一直将我的手机连接至计算机——那么我的建议是打开
Android Studio,找到AVD(Android 虚拟机)管理器,你将在启动界面上找到Configure
菜单,并创建自己的Android 虚拟设备。建议创建一台Pixel 2 虚拟设备,使用API 级
别28(请确认已安装对应的SDK,可以通过Configure 菜单找到SDK 管理器来确认)
并设定分辨率为1080×1920(420dpi)、目标为Android 9。选择一种x86(ABI x86_64)
镜像。长久以来,Android 虚拟机在性能方面臭名远扬,但此类虚拟设备表现异常优
异,几乎可以媲美原生性能。尽管这么做可能没什么必要,但还是请为其配置512MB
的SD 卡。大部分设置使用默认大小即可满足需求,但API 级别和CPU 类型比较关键
(需要进行特别设定)。
完成上述设定后,你已准备好随时通过Android Studio 来运行Flutter 代码了。也
可以通过命令行基于SDK 来运行,不过我不会过多描述如何通过命令行来操作SDK,
除了flutter doctor 之外。在本书的剩余部分,我们都将在Android Studio 中完成一切。
注意,当你再次运行flutter doctor 时,你可能仍旧得到错误提示,说找不到可用
的Android 设备,这可能是因为你刚刚创建的虚拟设备还没有运行起来。尽管如此,
flutter doctor 应该能检测出原因,并向你展示一个干净又健康的状态清单。最终,如果
虚拟设备确实没有运行,并且你也没有将一台真实的Android 设备连接至计算机,但
只要这是flutter doctor 报告的唯一错误,你就可以继续前进。
想了解iOS 是什么状况,先别急!因为我们使用的是Android Studio,通过它所能
做的事,无论操作方式还是文件格式,都不可能运行在iOS 上。只有以下场景才需要
考虑这一点:当你希望在一台真实的iOS设备上进行测试,抑或部署的目标设备是iOS,
抑或构建用于分发的应用时,你才需要一台Mac 机器并安装好Apple 的Xcode IDE。
本书不涉及iOS 或Android 的应用分发主题,因此模拟器就可以满足我们的需求。
1.9 (不那么)经典的“Hello, World!”应用
如果继续遵从Flutter 网站上的安装指导,你将在最后一步创建一个小型的Flutter
应用。尽管文档中的讲解非常棒,但我还是建议你先跳过这个步骤,让我来带你实际
体验一遍。
首先,让Android Studio 自动构建一个应用(在SDK 的协同下)。这个过程相当简
单,当我们在虚拟设备上把这个应用运行起来时,我们将稍作修改并一睹热重载如何
发挥作用。
但是,我们得先创建一个项目!当首次启动Android Studio 时,你会看到如图1-1
所示的界面。
图1-1 启动Android Studio
看到Start a new Flutter project 这行文字了吗?这正是我们需要的,单击它!你将
看到如图1-2 所示的应用创建向导。
图1-2 选择需要创建的Flutter 项目类型
你可以创建四种类型的Flutter 项目:
● Flutter Application(贯穿本书所使用的类型)
● Flutter Plugin(一种可以导出原生Android 或iOS 功能到使用Dart 语言开发的
Flutter 应用的插件)
● Flutter Package(只有在你想发布一个独立于应用的自定义微件时才需要选择
这种类型)
● Flutter Module(可以将Flutter 应用嵌入原生应用)
选择Flutter Application 并单击Next 按钮,你将看到如图1-3 所示的界面。
图1-3 输入应用所需的必要信息
在这里,你需要输入刚刚创建的应用的一些必要信息。默认值也可以,或者按你
喜欢的方式对项目进行命名,以及修改相关描述。同时,你可以修改项目的保存路径(保
留默认值也行)。看到底部的错误提示了吗?如果没有错误提示,那更好,说明你已经
正确配置了系统环境变量。但是,如果遇到错误提示,那是因为Android Studio 还不
知道你的Flutter SDK 安装在哪里,你必须告诉它。单击SDK 路径输入框右侧的按
钮,浏览已经安装好的SDK 路径并选中,确认Android Studio 加载成功(你会发现错误
提示消失了),再次单击Next 按钮,我们将看到如图1-4 所示的界面。
图1-4 新建项目的最后细节
在最后一个界面上,你需要填写一些额外的信息,主要是公司的域名。当然不一
定非得是公司,关键是需要填写规范的点分格式的内容,也就是互联网域名。如果还
没有一个域名,你可以输入任何你喜欢的内容。关键是:输入任何对你来说有意义的
内容,同时注意包名会随之发生改变——把项目名和你在最后一个界面上填写的域名
拼接在了一起。如果希望在应用商店中分发这个应用,那么包名必须保证唯一性,不
过既然我们在此以测试为目的,所以叫什么都不要紧。
■ 注意:
你也许还能看到一种名为Sample Application的项目类型,这取决于所安装的
Android Studio和Flutter插件的版本。能否看到都无所谓,如果需要的话,它可以帮你
创建一些模板代码,但本书用不到,因此无论有没有这个选项都无关紧要。
最后,你需要在平台所支持的编程语言中做出选择。一般来说,比较常见的是不
选Kotlin 而选中Swift。这里指的是在Flutter 框架下使用的平台特定的语言,除非在
应用中需要与原生代码交互,否则对你来说没什么不同。
设定完成后,单击Finish 按钮,Android Studio 会快速生成一个简单的Flutter 应
用。这花不了几分钟,你可以观察底部的状态栏以确保所有任务已完成。完成后,查
看Android Studio 工具栏的顶部,找到用于列出已连接设备列表的下拉菜单,如图1-5
所示。
图1-5 Android Studio 中的虚拟机下拉列表
你会看到前面创建的模拟器。选中它,如果还未运行,那么很快就会启动它。运
行起来后,单击写着main.dart 的下拉列表旁的绿色箭头(这是应用的入口)。然后,稍
等一会儿,等待应用完成构建、部署并运行在模拟器上(这取决于你的机器配置,可能
需要一分钟左右,不要急——不过首次构建之后就会快很多)。之后你会看到如图1-6
所示的界面。
图1-6 这是我的第一个Flutter 应用
这个应用虽然简单,但却展示了大量内容。单击底部带有加号的圆形按钮(即悬浮
按钮或FAB),你会发现每单击一次计数就会加1。
Android Studio 将自动打开已生成的代码,形如以下代码清单(不过请注意,我已
经移除注释并重新格式化了部分代码,希望便于阅读):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children:
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
代码量虽然不大,但还是涉及很多内容。此刻,你还没有掌握足够的知识点,也
就是说还不能深入理解,毕竟我们还没开始讲解Dart 的细节。不过我也没打算完全不
做任何解读而让你自行领悟,因此我会指出其中比较关键的几点。
首先,请注意每个Flutter 应用的入口都是main()方法,它将简单地调用由Flutter
自身提供的runApp()方法,并传入顶级的根微件。总会有一个微件位于层级的顶层,
并包含其他所有微件,这里是一个MyApp 类的实例。这个类碰巧是前面讨论过的无
状态微件类,因此其唯一需要做的就是提供build()方法,正如你所看到的那样。build()
方法返回的微件(请牢记,build()方法返回的永远是一个子微件或有或无的微件)是一个
MaterialApp 类的实例,该微件也是由Flutter 提供的(你可以在代码的顶部看到它是从
flutter/material 导入的)。我们将在第3 章深入讨论这个微件,但关键是它提供了Material
风格(也就是Google UI 风格)应用的基础框架。你可以看到它设置了标题(MaterialApp
构造方法的入参之一),用于在应用运行时显示在状态栏上。你还可以看到,你能为
Flutter 应用设置主题和细节,比如主题的主色调,此处设置为蓝色。
该MaterialApp 微件有一个子微件——一个MyHomePage 类的实例。Dart 有点儿
奇怪的地方在于,当你实例化一个类时,你不需要像大多数面向对象语言那样提供一
个new 关键字,就像这里所展示的那样)。
MyHomePage 类定义了一个有状态微件,在这个例子中我们需要两个类:继承自
StatefulWidget 的核心类,以及与之关联的继承自State 的状态类。_ MyHomePageState
就是那个状态类,虽然看上去有点儿怪,但它也是一个微件,你可以从它拥有build()
方法看出这一点。你的第一个直觉可能是:build()方法应该在MyHomePage 内,同时
_MyHomePageState 应该仅包含描述MyHomePage 微件的数据,但事实恰恰相反。
无论如何,build()方法再次返回了一个微件,这次是Scaffold 微件。你仍无须考
虑它是一个什么微件,我们将在第3 章深入讨论。但简要来说,它为应用提供了基本
的可视化布局,包括显示标题的状态栏(实际上是一个AppBar 微件),等等。Scaffold
同时还提供了绑定FAB 的能力,也可以说,将FloatingActionButton 微件的实例,作
为入参floatingActionButton 的值传入Scaffold 的构造方法。
另外一个传入Scaffold 的构造方法的参数是body,我们将其他微件作为子节点添
加至此。在这里,你将开始见证“一切皆微件”:我们有一个Center 微件,它是一个
容器(你也许已经猜到了)并居中设置了唯一的子微件。此例中,child 是一个Column
微件,它是Flutter 提供的众多布局相关的微件之一,该微件将其所有子微件排成一列。
这里的Column 微件的两个子微件都是Text 微件,一个用于展示文本“You have pushed
the button this many times: ”,另一个用于展示单击按钮的次数。
在接下来的两章中,我将不断深入探索Dart 和Flutter,这一切将逐渐明朗。尽管
我省略了大量细节,但我还是觉得以上解读足以让你产生适度的理解(另外,我移除的
那些注释实际上很有用,它们提供了很多有用的信息,当你自行生成这个应用时请记
得仔细阅读)。
1.10 热重载:你会喜欢上它的
从现在起,一切将变得难以置信。先确保应用已在模拟器上运行起来,再打开
Android Studio 并找到如下代码:
Text(
'You have pushed the button this many times:',
),
随便修改下,比如将button 改为FAB,并按Ctrl+S 快捷键或选择File 菜单下的
Save All 选项以进行保存。现在,观察模拟器,几乎在同一时间你看到屏幕也发生了
变化(也许要花几秒时间,但绝对比首次运行快多了)。
棒极了,是吧?
热重载仅工作于调试模式,从应用的右上角的调试条幅中我们可以看出。在调
试模式下,应用运行在Dart VM (虚拟机)上,而不是编译好的本地ARM 代码,只有
用于发布的应用才会编译为本地代码(因此,在调试模式下,应用的运行会有一点点
慢)。热重载通过将修改过的源代码注入正在运行的应用宿主Dart VM 而生效。注入
后,VM 将更新所有因改变成员变量或方法而引起变更的类。然后,Flutter 框架触
发一次微件树的重建,于是你的变更被自动响应。你不需要重新构建应用、重新部
署或重启任何东西,所有的事情将按需自动完成,以使变更以最快的速度反映在屏
幕上。
尽管很少发生,但你可能偶尔会碰到热重载并未如期响应某些代码变更的情形。
如果碰到这种情况,先尝试单击工具栏上的Hot Reload 图标,它看起来像闪电,如图
1-7 所示(也可以在Run 菜单中找到Hot reload 选项,快捷键是Ctrl+/):
图1-7 Android Studio 中的Hot reload 图标
这应该能解决前面的问题。同时请注意控制台窗格,通常位于Android Studio 下
方并自动显示,你会看到一些输出内容,如下所示:
Performing hot reload...
Reloaded 1 of 448 libraries in 2,777ms.
同样,当热重载执行时你会发现一个小的气泡提示,显示在控制台窗格的旁边。
现在,一件关于热重载机制的令人振奋的事情是:应用的状态被保留了下来!你
可以通过单击几次FAB 按钮,并且观察到文本确实改变了来确认这一点。换句话说,
单击按钮的计数值在重载后保留了下来。这使得改变UI 的同时保留其当前状态变得
非常容易,因此你可以在两种设计中轻易地进行A/B 测试。但如果不想保留状态呢?
在这种场景下,需要热重启。你可以通过Run 菜单中的Hot restart 选项或使用快捷键
(Ctrl+Shift+/)来激活它,但必须手动执行(相对而言,热重载会在你改变代码并保存时
自动执行)。
有趣的是,工具栏上并没有提供热重启的图标,不过无论如何,这都将重启你的
应用并清除状态,只是没有重新构建。
你当然可以在任何需要的时候执行一次构建(执行Run 命令),但这将引起一次完
整的编译过程,因此会比较慢。热重启几乎像热重载一样快,因为它所做的工作少得
多,却带来几乎相同的效果(当然,除去代码变更的因素——你需要为此执行构建,或
者默认情况下通过热重载来完成)。
希望你能明白热重载的好处,开发者可以使用它进行高效开发。我想随着对Flutter
的理解不断加深,你一定会感激它的。
1.11 Flutter 应用的基本结构
本章的最后一个话题是自动生成的应用结构,基本的文件夹结构如图1-8 所示。
图1-8 基本的文件夹结构
如你所见,有5 个顶级文件夹,它们分别是:
● android——这里包含了Android 平台特定的代码及资源,比如应用图标、Java
代码、Gradle 配置及临时资源等(Android 使用Gradle 作为构建系统)。其实,
这就是一个标准的Android 项目,你可以使用Android 工具链来完成构建。大
部分情况下,你只需要修改图标(位于android/app/src/main/res 目录下,其中每
个子目录都代表一种不同的分辨率)。通过android/app/src/main 目录下的
AndroidManifest.xml 文件,可以按应用所需设置相应的Android 平台特定的应
用属性。
● ios——同android 目录一样,该目录包含了iOS 特定的代码。关键的不同在于
ios/Runner/Assets.xcassets 目录,iOS 应用所需的平台特定图标都放在这里;至
于ios/Runner 目录下的Info.plist 文件,其作用等同于Android 应用的
AndroidManifest.xml 文件。
● lib——尽管第一次接触时会觉得有点奇怪,但它就是应用代码所在的目录。在
这里,你可以用你希望的方式自由组织代码,创建你喜欢的任意目录结构。
但你需要一个文件作为入口,大多数情况下,自动生成的 main.dart 文件承担
了这个职责。
● res——这个目录包含了一些资源,比如应用需要的国际化字符串资源。本书
不会涉及此项内容。
● test——在这个目录中你会找到一些Dart 文件,用于执行针对应用的测试。
Flutter 提供了Widget Tester 功能,它可以使用这些测试来确保微件功能的正
确性。与res 目录一样,本书也不会涉及测试方面的内容,因为在Flutter 开发
中这是可选的,而且单就测试本身都足以写一本书了。当然,测试相当重要,
但在你学会开发Flutter 应用之前,你无须测试任何东西,本书将聚焦于掌握
开发技能这一目标。
在Android Studio 中默认被隐藏显示的.idea 目录中存储了Android Studio 的配置信
息,你可以忽略它(从Android Studio 源自IntelliJ IDEA IDE 可知,.idea 目录因此得名)。
此外还有build 目录,用于存储在使用Android Studio 和Flutter SDK 构建应用时用到
的一些信息。通常可以忽略它们。
除了以上目录外,你还会在项目的根目录中看到一些文件。有一些位于lib 目录
之外的文件需要关注,它们是:
● .gitignore——Git 版本控制用来管理不需要纳入版本控制的那些文件。开发
Flutter 应用时是否使用Git 是完全可选的,但该文件无论如何都会自动生成。
源代码控制本身是一个完整的话题,本书同样不会涉及此项内容,因此也可
以忽略。
● .metadata——Android Studio 用于记录项目的数据。你可以忽略它,因为你永
远不会手动编辑它。
● .packages——随Flutter 提供的包管理器用于管理项目的依赖。这个包管理器叫
作Pub,该文件就是Pub 用来管理项目的依赖的。你不会与它发生直接的交互,
甚至也不会直接用到Pub,所以也可以忽略它(直接通过命令行使用Pub 并非
绝无可能,但在Android Studio 中确实少见,随Flutter SDK 提供的命令行接
口已抽象并隔离其中的大部分操作)。
● *.iml——这是Android Studio 的项目配置文件,它将以你的项目命名。你永远
不会直接编辑它,因此可以忽略它。
● pubspec.lock 和pubspec.yaml——你用过NPM 吗?你熟悉NPM 使用的
package.json 和package-lock.json 文件吗?是的,这就是Pub 用于同样目的的
文件。如果不熟悉NPM,那么可以使用pubspec.yaml 向Pub 描述你的项目及
其依赖。pubspec.lock 文件是在Pub 内部使用的。你一定会直接修改pubspec.yaml,
而绝不能修改pubspec.lock,稍后将介绍pubspec.yaml 的细节。
● README.md——你可以按自己喜欢的方式自由使用的自述文件。通常,
GitHub 之类的网站使用这个Markdown 文件来展示项目信息,当你浏览一个
仓库时,就会发现它位于根目录中。
到现在为止,pubspec.yaml 是最重要的文件,它也是少有的几个你需要编辑的文
件之一,所以你可以忘记其他事,但一定要记得它!稍后当我们需要添加一些依赖到
项目中时,我们会用到它,但此刻,自动生成的文件足以满足我们的需求了。
1.12 其他一些“隐藏的”细节
打开ios 目录,看一下其中的文件,你会发现有一个文件名含有Runner 这个词。
这提示我们构建为发布版本的Flutter 应用是怎么运行的。正如前面提到的,热重载可
以运行是因为你的代码是以调试模式运行在VM 上的;而在编译为发布版本时,热重
载就不能工作了。这时,你的代码将被编译为本地ARM 代码。事实上,你的代码将
被编译为一个ARM 库,这也解释了为什么你的代码位于lib 目录中。
Flutter 引擎的代码和你的应用代码,在iOS 上是使用LLVM(低级别虚拟机,一种
编译器架构,使用C++编写,是为了对编译期、链接期、运行期以及“空闲期”使用
任意编程语言编写的程序进行优化而设计的)提前编译(AOT)的,在Android 上则使用
原生开发包(NDK)编译为一个本地ARM 库。这个库能被一个叫作runner 的本地应用
加载,也正是runner 在运行你的应用。可以把runner 理解为一个轻量的运行器,它知
道怎么运行你的应用,并为之提供一些服务。在某些方面,这个运行器表现得像一个
VM,尽管相当轻量级(几乎等同于一个Docker 容器,如果熟悉的话)。
最后,这个运行器和预编译库都被打包进iOS 平台的.ipa 文件,或被打包进Android
平台的.apk 文件,于是你便获得一个完整的、准备好发布的安装包!当应用被启动后,
运行器加载Flutter 库以及你的应用代码,从这一刻开始,所有的渲染、输入输出事件
处理将被委托给编译好的Flutter 应用。
■ 注意:
这与大多数移动游戏引擎的运行方式相似。我以前写过一本关于Corona SDK的
书,Corona SDK的运行方式与Flutter非常相似,尽管它使用Lua语言而非Dart语言(我打
赌Flutter团队曾考虑将Lua列为备选语言)。我觉得这很有趣,Google最终从游戏引擎获
得灵感来设计Flutter,这也证实了我一直所说的:要想成为一名合格的开发者,就写
游戏吧!这是用来磨炼技能的最合适的项目类型。继续往后看,你会发现本书的最后
两章将聚焦于使用Flutter构建一个游戏。
1.13 小结
本章开启了你的Flutter 之旅!你了解了Flutter 是什么,它能做什么,以及选择它
的理由(甚至一些不选择它的理由)。你掌握了一些关键的概念,比如Dart 和微件。你
学会了如何设置开发环境以开发Flutter 代码,还创建了第一个简单的Flutter 应用并在
模拟器上运行它。
在下一章中,你将掌握更多Dart 知识点,建立良好的基础体系,以便在不久后可
以使用Flutter 开发一些真正的应用!