2020 年初我在朋友圈发了这样一段话(略有编辑):Application 的三种形态 Native、Web、Hybrid 很早就提出来了,其中 Hybrid 型态因其跨平台特性和能够轻松创建一致的 UI 体验而备受关注,催生了不少框架。但是,一个框架要成为标准,要发展出生态,那是个漫长的过程。混战近十年,小框架要么死要么合并,有大平台烧钱的才能支撑下来。Microsoft 很早就涉入了 Hybrid 框架,与 Facebook、Google 等后起之秀并存至今,你要是常看电影可能一眼就能觉察到谁是影片的主角或是主角之一,所以,我觉得现在可以折腾一下 Flutter。

于是,我便在业余时间里学习了 Flutter。我觉得学习的意义是在于明白 Flutter,形成一个有效的认知,在实际使用时知道如何使用其能力,能够在架构层面应用 Flutter 设计跨平台的现代应用程序,以至于对应用程序的用户体验、效率、生产力等起到正面的促进作用。

1,初见

通读官方(英文)原文文档,Flutter 一遍,Dart 一遍。基本上都是在早上,用手机打开官方网站阅读其文档资料。Flutter 还专门对不同平台的开发者写了迁移指引,像 Android 平台,文档就介绍到原生 App 里的 View 和 Flutter 里的 Widget 最接近,这些文档可以让其他平台的开发者更容易上手。对于 Android 或 Web 开发者而言,大部分的知识都已经具备了,读一遍文档大概一周就可以完成。

读完文档其实没多久就忘了,不过,能读懂能读完说明能继续往下走,用到时查阅即可。如果读不懂文档那可能很难继续,应该先提升一下技术知识。

第一个演练是官方样例,准备环境、创建项目、运行都一切顺利,体验 Hot Reload,感觉很不错。我是走国际线路来安装环境的,Flutter 有国内镜像,但是国内网络有点复杂,我甚至换一家咖啡店就没法访问 Flutter 的国内镜像,所以 Flutter 的开发就一直走了国际线路。

有印象的是调整 Flutter 项目的目录结构,Flutter 项目的目录结构比 Android 原生 App 项目多一层,即顶层是 Flutter 项目,然后里头包含了 Dart 源代码和 Android、iOS、Windows 等平台子项目。对于 Android 开发者而言这种结构看着有些奇特,于是费了一下午的力气修改 Gradle 构建脚本使 Flutter 和 Android 在同一层,结果,VSCode 或是 Android Studio 调试 Dart 时均无法构建 App,仅能从 Android 子项目构建 App。搜索了很多资料经过多方确认得知项目有些路径都是固定的,构建环境根本没有完全按脚本来配置,有些只能按照默认的来。

调整目录结构的想法就此搁置,为了让项目模块化一些,后来想了个办法,把平台代码封装放到 Android AAR 里给 Flutter 的 Android 模块调用,以实现目录结构不受 Flutter 工程的影响。

It's about the network
Flutter 列表,数据均由程序随机生成

2,演练

此前做过一个 Shadowsocks Android 的定制版,后来没有维护了,因为要用 Flutter 全新设计一个 App。这个 App 会有一个比较复杂的列表,列表展示数据库的数据,需要能够支持添加数据、删除数据、修改数据,交互上还要有调整显示顺序、选择 Item 的功能,此外还得设计一个空数据时的交互界面,接下来的深入学习和演练都是围绕这个列表和数据库而展开。

数据库我用的 AndroidX Room 组件,它封装了 SQLite 的一般操作并且支持 DAO(数据访问对象),通过类和注解就可以创建数据表或建立关系表还可以完成一般的数据存取,不需要写 SQL 语句。其实 Flutter 也有 SQLite 插件(Flutter 的插件后面还会提到),但是不支持 DAO,所以我的设计方案是将数据库操作放到 Android 平台,通过 Flutter 平台通道来传递数据交互。

这样的方案把 Flutter 的很多组件都涉及到了,包括平台通道、动态、异步、布局等,在官方文档和搜索引擎的帮助下用了数天的下班时间最终实现了这个方案,数据库和交互均符合预期。感触不少:Flutter 的异步(await, async)感觉真是太熟悉了,关键字及用法都和 C# 里一样;dynamic 和 object 不好理解,刚开始很难判断该用 dynamic 还是 object;用下划线“_”省略参数真是一种难看的设定,和 Kotlin 一样难看。。。不过最有映像是框架的效率,我用几年前的手机跑 Debug 版本也很流畅。

3,一个月

将列表的实现完善、整理并封装其实就完成了一半的交互界面了,剩余的部分都比那个列表要简单很多。所以,接下来的工作就以平台通道为桥梁,拓展交互界面和平台能力实现。理论上 Dart 界面部分是跨平台通用的,而 App 能力或 API 调用则依托于操作系统平台,这可以为 App 后续跨平台打下基础,在不同的系统平台上实现同样的能力,而又不影响 App 架构。现在,我的这种“下班工作”已持续近一个月。

Flutter 设计了 StreamBuilder、FutureBuilder 来支持 UI 异步更新,而 Android 原生做异步更新可能是 AsyncTask 或运行 Thread 完事后调用 runOnUiThread 等方式,Flutter 这种没接触过的全新组件也让我走了一些弯路,不好判断使用场景,以至于在不必要的场景使用了异步组件。后来在 Stackoverflow 上看到一个回答总结的很好,大意是说 StreamBuilder 用于持续性数据更新,而 FutureBuilder 适用于一次性的数据更新。其实组件名称早就说明了,一个是 Stream 一个是 Future。

我在列表上方放了一个图表 Widget,以实时显示设备的网络流量,数据由 Android 平台端主动更新并发给 Flutter 显示。起初是在页面订阅平台通道事件并控制 Widget 更新,这使得页面的刷新愈加复杂,也是在这个时候,发现页面中的一个 Widget 调用 setState 刷新整个页面时可能会打断另一个 Widget 正在进行的界面交互。即当我拖动列表项试图调整排序时,来自 Android 平台端的流量数据更新会中断我列表项的拖动。

后来把平台通道的事件订阅交给图表 Widget,让 Widget 自己管理其状态和刷新即完美解决。其实中途还尝试让“控制点”使用 Widget 的 Key 来更新数据,打断交互问题解决了但是 Widget 不显示的时候也被控制更新数据,总之那种设计和框架不是那么契合吧。这也很自然,至少在这个有点特别的场景里,以往“控制点”的思路不合适,新事物有它自己的 Style。

It's about the network
下班工作

4,三个月

接下来的几周陆续添加了 CameraX、ZXing、MLKit、GeoLocation 等功能模块,交互界面新增支持了 Light、Dark 模式,可以跟着系统自动切换也可以手动控制,此外还设计了 App 大致的页面结构,一个 Flutter App 大致成型,当然,不少 Native 层的模块需要添加,也有很多细节需要处理。

这里顺便说一下 Android 的摄像头 API,Android 目前有 Camera、Camera2、CameraX 三种 API。其中,Camera 已不推荐使用,它比较老旧有些新的硬件特性可能无法使用或伴随性能问题;Camera2 是 Android 5.0 新加入的 API,能更好的使用硬件能力和提供更好的性能,但是 Camera2 变化较大而且使用繁琐;CameraX 是 Android Jetpack 的一部分,Google I/O 2019 提出的最新的摄像头 API,它具备 Camera2 的特性而且对开发者也更加友好。

CameraX 能很好的对接图像处理模块,如 ZXing、MLKit 等或是开发者的图像处理算法。我的 App 也是这种模式,CameraX 加 ZXing 或 MLKit 图像分析来实现扫码能力。不过交互上是 Flutter 通过平台通道打开一个 Activity 来做这些事情,其实应该要用 PlatformView 更合适,总之有不少可以优化的地方吧。

随着逐步改进和优化,时间来到了三个月,感觉 Dart 和 Java 在编程上有个不一样的地方,在大的编程思路上 Dart 更倾向于面向过程,可以轻松的通过全局变量和函数来组织页面,而 Java 等面向对象编程则倾向于使用抽象和对象的关系来描述事物。三个月,回顾过往,发现不知不觉写了几千行的 Dart,现在,用 Flutter 设计及开发 App 已经不是问题。

5,Flutter Windows 应用

2020 年 9 月份,发布了 Flutter for Windows Desktop 的 Alpha 版本,也就是说可以用 Flutter 开发 Windows 桌面应用程序了,将 Flutter SDK 切换到 Dev 通道即可创建支持 Windows 平台的 Flutter App。如果需要让 App 支持 Windows 平台,在 Windows 上实现相应的功能即可,Dart 部分是通用的而且在各个平台上也有一致的 UI 呈现和交互体验。

当然,不同的屏幕(信息输出)和不同的信息输入方式(触屏、鼠标、键盘)在交互上给人感知和体验是不一样的,Flutter App Dart 部分虽然通用,但是也得根据屏幕和设备类型在交互上做适当的适配。

在发布 Alpha 的同时官方也给出了一个 Flutter Windows Demo 软件,我体验了一下感觉很不错,运行 Demo 并不需要任何运行库或是虚拟机,App 应该算是 Win32 SDK 原生 Windows 应用程序,稳定而且效率很好。引人注意的是在任务管理器里能看到 App 能将图形部分的计算交给 GPU,UI 变化时能看到 App 的 GPU 使用也会发生变化,其实,Flutter 是做了不少图形相关的工作才让它具备了这一让人兴奋的特性。总的来说,Alpha 就给到了开发者很大的迁移动力。

我试着将之前的一个 Windows .NET WPF 开发的小程序用 Flutter 重构,感觉开发上和 Android 没什么差别,编写 Dart 只需要关注 UI,不用关心是什么操作系统,而系统平台相关部分由平台层处理。

It's about the network
Flutter Windows

6,插件和生态

Flutter 的插件能够让 App 轻松扩展功能或是和操作系统进行交互,但是目前来看却不是那么美好。开发者封装一些平台实现代码及 Dart 代码并打包为插件发布,让其他开发者直接使用,这样可以提升开发效率。但是,引入插件较多时会让 App 的模块化组织混乱:一方面,各插件功能可能会有重叠部分,每个插件都创建平台通道会带来不合理的资源开销;另外一方面,各插件的依赖项可能引起冲突,插件指定的构建工具版本零散,这些都会给 App 的构建带来难以预测的结果。

尤其对于系统平台较为生疏的开发者,依赖大量的插件可能是让人沮丧的,如果对平台原生开发熟练,能自己管理平台端实现及数据通讯,那么插件可能就没什么意义。起初,我也引入了一些插件,结果发现那些插件只是封装一些简单的 Android 原生代码,然后就完全放弃了插件,自己建一套平台交互机制。Dart Package 则较为单纯,不会有什么影响。

有人评价 Flutter 的生态不好,而我觉得现在去评价 Flutter 的生态为时尚早。Android 系统在 2.x 才有一个相对稳定的状态,到了 5.x 才开始有了比较明朗的发展线路。Flutter 的插件生态我估计后续可能会有比较大的变化,就目前而言,有很多开发者参与我觉得就是一种健康的状态。

7,2021

回顾 2020,如果要说最大的收获那应该是:掌握了 Flutter,掌握了现代跨平台应用程序的设计和开发,能够连接未来新一代移动应用技术主线,驾驭创新。

2020 不容易,2021 希望能够助你乘风破浪,所向披靡!