我是大概这周的前几天知道Dart 2要推出而且Flutter进入Beta的。

说实话,Dart我有所耳闻,虽然我是一个Google的粉丝,但是话还是要说清楚,我真不认为Google在设计语言上有什么优势,甚至是并不擅长设计语言的(Go语言我浅尝辄止),而且Dart本身设计我认为也有问题(居然同时存在constfinal关键字),我也只是粗略的看了一下语法就停了。

然而,在今晚的旅程过后,我决定把Flutter和Dart加进学习日程表(虽然优先级不会太高),因为Flutter颠覆了我这个“零经验”移动开发者的观念。这篇文章,就让我零零散散的谈谈今晚的旅程。

0x00. What is Flutter?

那首先,什么是Flutter

在讲Flutter之前,我们不得不提React Native,熟悉前端和移动端开发的同学肯定对这个东西有所了解。React Native是由Facebook在两年前推出的一套跨平台开发框架,使用React+JavaScript的组合来开发移动端应用。

当年我听说RN的原因是它的热更新:这套机制允许我们不用更新应用本身,而是通过云端推送更新的方式来对应用做出更新。

RN的出现的确给移动端开发掀起了一轮新的浪潮:跨平台、热更新、JavaScript。这样的组合,无论是对前端开发者还是移动端开发者都是极具吸引力的。同样,不少公司已经采用了这套框架,如我们熟悉的QQ Android端和手机京东iOS&Android端。

RN这一套机制实际上很容易猜测到的:底层是一个native app,这一个app负责进行传统的系统交互,比如调用startIntent()方法等,然后上层是一个javascript bridge,这一个东西是一个桥梁,负责和底层的native app以及上层的表现层交互。上层的表现层就是用户看到的内容,不同框架的实现机制不同。

神の1番目破片

图片来自 InfoQ

在RN之后出现的框架有:Weex、cordova等,以及,*Flutter*。

在2015年的Dart developer summit上,“Sky”,也就是现在的Flutter被首次提出,然后在2017年Google I/O上亮相之后,与近期推出了Beta。

Flutter的底层实现比起上述的框架们又有所不同:它不再需要任何的Bridge,而是通过一个C++写就的高性能底层框架来进行以下两种操作:UI绘制和事件监听。

神の1番目破片

图片来自 InfoQ

而且,Flutter还有另一个特性:没有使用任何系统自带的组件,而是自己实现了组件库(Widgets),这样一来,UI 本身不需要去适配平台,而是只需要做好渲染层和平台的连接即可,实现同样的效果的成本也就更低。

这样一来,Flutter就和上述的以React Native为首的框架不同,不仅具有高性能,同时还有出色的、漂亮的、大量可复用的UI组件,能够帮助我们快速开发跨平台的移动端应用。这无疑是极具吸引力的。同时,Flutter也将成为Google最新的操作系统,Google Fuchsia的默认开发语言。

关于Flutter的讨论还有很多,文末一并附上。

讲了这么多,下面来实际演练一下。

0x01. SDK安装与编辑器配置

SDK的安装实际上是比较简单的,一波三折的部分主要在Android开发工具链的配置上。

1. Flutter SDK

首先要注意的是,Dart本身已经被包含在Flutter内,不需要安装Standalone的Dart VM。

对于中国用户,Dart Team很友好的提供了加速安装的方法

1
2
3
4
5
6
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
git clone -b dev https://github.com/flutter/flutter.git
export PATH="$PWD/flutter/bin:$PATH"
cd ./flutter
flutter doctor

执行完以上命令后,flutter会自动生成一个报告来告诉你的本机配置如何,缺失什么配什么。

这里我缺Android工具链(换到Manjaro之后没有配置Android开发环境——因为我在Elemenrtary下就没写过Android),所以我经历了痛苦的Android SDK配置过程。这个安装大家各显神通,要写就应该是另一篇文章了。

之后是配置Flutter工具链。工具链配置其实就是老路子:修改环境变量。

1
echo 'export $PATH="$HOME/env/flutter/bin:$PATH"' >> ~/.zshrc

重启一下终端,安装就完成了。

要注意的是,上面设置的镜像https://pub.flutter-io.cn是临时性的,这里强烈建议大家把镜像也一起添加进去,不然之后装包的时候会各种卡死。

1
echo 'export PUB_HOSTED_URL=https://pub.flutter-io.cn' >> ~/.zshrc

要注意的是,官方提示到这个镜像是由GDG China负责维护的,Flutter团队并不保证可用性,所以各位记得出现了问题及时更换镜像。

2. VS Code配置

官方推荐的开发工具有两个:Android Studio和VS Code。作为VS Code重度用户,我当然选择了VS Code。

VS Code的配置完全没有任何难度,来到扩展中心安装dart code即可。

之后使用命令面板Flutter: New Project新建一个Flutter项目,就可以开始愉快的Flutter开发之旅了~

注意,开发Flutter需要使用Android或者iOS模拟器,iOS我不知道,对于Android,如果你在Windows下,使用任何模拟器并正常连接ADB就行;如果在Linux下,要么就再开一个Android Studio启动一个模拟器,要么就直接使用Android Studio开发,要么就像我一样,直接上真机。(Android Studio + 模拟器真的吃内存,我要报警了。

0x02. 官方示例分析

让我们分析一下官方示例项目的一个简单结构:

1
2
3
import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

首先,一个程序的入口一定是main(),在Dart中,这个函数是没有任何返回值的。对于第三行的这个写法,实际上是如下写法的简写:

1
2
3
void main(){
  runApp(new MyApp());
}

那这其实就是一个语法糖,创建单语句函数的语法糖。

然后是包的导入。要注意的是,导入第三方包的结构是:

1
import 'package:<package name>/<entry file>.dart';

我们继续:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.green,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

这一段语法很像Java,让我们来看看这里面几个值得注意的点:

  1. UI层的基本单位是一个组件(Widget),即使是最底层的App也不例外。

  2. 构造函数中的如下写法:

    1
    
    return new MaterialApp(title: 'Flutter Demo', //...)

Python同学应该一眼就能看出来:关键字参数。Dart的参数支持如上所示的关键字参数来对一些关键属性做一种Mapping指定。

  1. 这个MyApp继承自StatelessWidget,字面意思上来看,这是一种“无状态组件”,和下面的“有状态组件”其实是一种对应关系,我们下面讲。

  2. 一个组件创建要提供的一个必要方法是build(BuildContext context),每当这个组件被重绘的时候都会调用这个方法。

    1
    2
    3
    4
    5
    6
    7
    8
    
    class MyHomePage extends StatefulWidget {
    MyHomePage({Key key, this.title}) : super(key: key);
    
    final String title;
    
    @override
    _MyHomePageState createState() => new _MyHomePageState();
    }

这个组件就是一个“有状态组件”,有状态组件的重绘使用的是createState(),创建状态。

对于“有状态组件”,我们都要实现一个状态类来控制状态:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ), 
    );
  }
}

上面这个类做了什么?我们一步步拆分:

  1. 类信息:

这个类是MyHomePage的状态,所以自然是继承自State<MyHomePage>这个类;这个类提供了两个函数:代理函数_incrementCounter和必要的build函数;一个计数器变量_counter

  1. _incrementCounter函数:

这个函数很明显,每次调用的时候使用setState()来改变状态,setState()接受一个函数作为参数,这个函数就是很简单的一个闭包,每次让计数器+1。

  1. build函数:

那这里其实很简单,根据绘制树绘制一个Scaffold组件,包含两行文字和一个浮动按钮,浮动按钮监听按下事件,每次按下的时候执行_incrementCounter函数。

类的信息分析完了,接下来我们来分析两个重要的东西:有无状态和setState()

首先要明确一点,在Flutter中,任何组件都是不可变的(Immutate),这意味着一旦一个组件被绘制完毕,没有任何办法来修改这个组件当前状态。

无状态组件,也就意味着这个组件永久不可变,也就是说,这个组建的状态只有你在代码中写好的一种状态,无论如何不会发生改变。

有状态组件的改变,实际上并不是组件发生了改变,而是我们重绘了全新状态下的组件。也就是说,实际上的StatefulWidget是一个代理,作为真正的状态的一个代理。当状态发生了改变,Flutter会调用状态的build函数,然后把新的状态组件提供给StatefulWidget,然后绘制到表现层。

那么这里的setState()其实就很简单了,就是执行参数这个函数并通知框架状态发生了改变,框架调用build来重绘组件。值得注意的是,在其他地方做出修改系统并不会重绘,也就是说,通知系统状态改变的入口一定是setState()以及相应的一些其他函数。

换一个说法,StatefulWidgetState的一个实例,State组件本身在整个应用的生命周期都能存活。

分析完这里,我们再来讨论几个有趣的小细节:

  1. Flutter万物皆组件,连CenterColumnMainAxisAlignment也是组件,我们并没有设置任何“属性”,而是选择了属性对应的“组件”。
  2. 支持字符串替换:'$_counter',十分方便的一种字符串表示方法。

这就是这个demo给我们提供的全部信息了。

从上面的分析来看,Flutter的设计模式十分像React的那一套,整个过程是响应式的:根据用户的请求响应来处理业务和重绘UI,这一套设计模式也被证明在界面构筑上是一套十分高效的设计模式。

Dart的类Java和C++语法也让人很好上手,对闭包和匿名函数的支持也在意料之内。

如果感觉意犹未尽的话,这里还有Flutter官方的小教程,有时间我也会对这个教程进行分析,不过这个教程体现的信息实际上并不比这个demo多多少。

0x03. Then

毫无疑问,Flutter又是移动端一个新兴强势势力,而且背后是Google,Android的父亲,这样我们不用担心Flutter的未来发展路线,至少在官方扶持下,Flutter不会只是一个昙花一现的框架。

但是在现在的世界上,能保持一个项目活力的关键并不是后台多么硬,而是开源社区的活跃程度。Flutter是一个十分不错的框架,尤其是大部分代码都可以自定义的情况下,很容易hack进去根据自己的需要进行修改。官方层面上,无论是整个开发工作流、对Issue的响应速度还是对开源社区的友好程度都是一种很积极的状态,越来越多的包也进入了dart的包管理系统。

然而,业界仍然对于Flutter有不同的声音,既有看好的也有看衰的,我们似乎不能现在就对Flutter的命运下一个定论。同时,Dart这门语言我的评价仍然不高,很多地方的设计并没有很好的解决Java和C++的遗留问题,这可能也将成为Flutter发展的一个阻碍。

那从我个人角度来看,这一句话是我想说的:I’m going to fix an eye to Flutter.

作为一个“零经验”移动端开发者,Flutter惊艳了我,让我重拾了对移动端开发的热情。虽然Dart有很多不尽如人意的地方,虽然没有太多的包可用,但是Flutter的未来仍然是一片光明的状态。

所以,还有什么理由不立即开始学习Flutter呢?


致谢:这篇文章的几张图片来自InfoQ的为什么说Flutter是革命性的?,感谢,同时,这篇文章也很值得一读。

延伸阅读:知乎上关于Flutter的讨论有很多大牛参与了,兼听则明,很多回答的质量十分高,建议一条一条仔细看。

未来我应该也会写更多关于Flutter的文章,之后见~