我的 Flutter 之旅

为什么大部分编程语言生态的 GUI 都做不大?相比较原生移动应用,跨平台 GUI 的劣势如何?在这个时代,Web 开发还有吸引力吗?

在 20 年底的时候我第一次接触了 Flutter,当时正赶上谷歌在 B 站开设账号大肆布道,中文社区掀起了一阵热潮,遂跟着《Beginning Flutter》 和 《Flutter in Action》简单入了门 —— 当时还基本没有中文书籍,而现在这两本书都有翻译了,跟着这两本书的例子,认识了基本的组件、交互和滚动、弹窗和表单使用,也用自己的短链接和物品管理后端做支持,做了个增删改查的客户端。

当时的这两个后端都是用 Scala 写的 —— 在 3.0 之前 Scala 社区还没有凉透,但也每每受限于类型系统的束缚,加上 Play 框架和 Slack 别致的设计理念和高昂的使用成本,在终于读通了文档并且着手开始搬砖的时候,当每次拿起配置不高的笔记本写一行代码,就得等好久编译,更不用提解决一些特殊需求,每次解决都要在迷宫一样的 Scala 源码中挣扎好久,不过好歹有 StackOverflow,问题基本都能解决。但客户端就不一样了,在没有良好的 GUI 理念认识(比如 Web 前端)和 Native 开发经验的前提下,Flutter 的入门成本不可谓不高,且不说 iOS 和 Android 开发工具链、环境和网络准备的成本,基于 Dart 的 Flutter 也不是什么开发者友好的语言 —— Dart 代码像 Java 一样啰嗦且不灵活(或者说 Dart 就是 Java,Dart 的作者就是 Java HotSpot 的作者),它们需要开发者深厚的功底和对面向对象精辟的理解才能够避免冗余并建立弹性的、可理解的和可扩展的抽象,以最佳限度的复用代码。很显然,在当时我还没有读过 SICP,没写过像样的 MVCC 架构的 UI 逻辑,因此糊出来的 Flutter 应用充斥着嵌套的括号,抽象程度差不说,代码可读性几乎为 0,并且莫名其妙的会因为组件渲染状态改变而请求多次 HTTP API。介于面对着这样的压力,终于我开发 App 的热情消失殆尽,正可谓三分热度,而写的那个客户端,就单纯的放在了 Gitee 上,https://gitee.com/corkine/cyberMeFlutter,在每隔 7 天就要重新验证一次开发者授权的 iPhone 上尘封了许久,也许是在哪次清理手机内存的时候不小心删掉后,终于不见了踪影。

但是回过头来看当初这个 App,功能上其实还不错,不论是可拖拽排序的列表页还是滑动删除和提示的交互,包括自动搜索补全、桌面短链接直接打开页面、调用相机插入并压缩图片、使用多个维度:上下滑动、左右滑动、点击、双击、长按/重按和简洁的界面完成复杂交互上,其实都还算不错,但却不论怎么也提不起来兴趣了。

时隔一年有半,自从工作以来,我大部分时间在搞 Web,不论是 Java 后端还是基于 Clojure/Script 的前后端混合开发,但 Web 的缺陷也正如同 JVM 的缺陷一样,其不能够很好的利用宿主平台的接口,比如应用图标右键、桌面 Widget 插件,对于相机、GPS 和位置传感器的直接使用,后台消息推送等等。而一个应用,最重要的就是能够让用户乐于使用,这些所谓的“辅助功能”其实在人机交互的情感分析中占据了极其重要的地位,而却被大多数不合格的产品经理和开发者所忽视。大多数 ToC 的应用客户完全可能因为一个糟糕的边距,一次意外的卡顿和闪退而卸载并拒绝使用服务,而也却会因为一段精美的动画、活泼亮眼的插图而愿意深入了解一个功能。因此,我总是想不起来写日记或者去做一件事,是因为 Web 界面并不立即可得,并且在上面写日记的体验很糟糕 —— 哪怕我先后实现了精美的界面、Markdown 支持和实时预览、图床支持和拖拽插入图片,在自己不是有强烈的欲望写日记的时候,也不会去碰它。而正相反,在我还在使用 DayOne 的时候,桌面小插件和极其顺手的写作体验能让我花费更少的时间去试图做一件事,哪怕打开一个 Web 界面也并不复杂,但相比较瞟一眼经常看的手机,从快捷菜单中滑动点击就打开相机拍照并记录文字还是逊色很多的。这种潜移默化的心理暗示会滴水石穿的影响对一件事的坚持程度,最终产生十万八千里的结果差异。

虽然我可以认识到这一点,但是因为开发哪怕基于开源组件实现的后端存储服务、前端界面和客户端,哪怕就一个简单的日记功能,也并不是一件容易的事情 —— 它们往往基于不同的编程语言,使用不同的库,有着不同的生态和最佳实践。Flutter 很好,React 也是,Play 亦是如此,但是当它们合起来,就变成了一件比较难的事情。这里并不讨论 React Native 或者 Qt 或者 JavaFx 之类的“跨平台” 解决方案,因为它们在除了自己原生平台外,基本上都达不到实现一个最低质量产品的标准。

事情的转机出现在我翻阅 Lisp 山脉后了解的 Clojure,基于 Clojure/Script 这一 Lisp 魔法的开发体验着实非常令人愉悦,包含单元测试、API 文档和结构良好的带有详细注释的代码结构的集成了 CI/CD 的 Clojure/Script 应用的生产效率非常之高。而我重拾 Flutter,也正是在看到了社区即将发布一个叫做 ClojureDart 的 Port,这帮人试图将 Clojure 编译到 Dart,以支持 Flutter 开发,它们已经在 App Store 上线了 Roam Research 这个 App,看起来非常的吸引人。一门表达能力丰富的语言,同时支持三大主流的跨平台应用,覆盖前端、后端和客户端开发,是多么美妙的事情,只要愿意,一个 Clojure 程序员甚至可以使用同一套业务逻辑在桌面基于 Election、JavaFx 和 Flutter 三门技术栈开发不同架构的程序 —— 从来没有语言做到过这件事,虽然大部分语言都试图这么做,尤其是每个野心勃勃的试图干掉 Java 的 JVM 方言一样。

二二年的五一遇上奥密克戎,无处可去,所以索性重拾了 Flutter,跟着 B 站“王叔不秃”的系列视频深入了解了布局、Key、异步、动画、列表和 Sliver,然后买了他写的那本《Flutter 组件详解与实战》的书,把一些常见和不常见的组件都混了个脸熟,又温习并深入思考了状态管理的东西:Provider 和 GetX,整理了沉寂多年的笔记,算真正的入了 Flutter 的门。这次“二进宫”后,已经能够比较熟练的利用裁剪、动画计时器、渐变过渡模拟健康码跑马灯的效果,能够使用一些共享的插画制作出来还算可以的简洁、一致的界面了。我把自己之前使用 Web 写的大屏(微软待办、HCM 系统、习惯养成系统)迁移到了 Flutter 上,看起来还不错,准备把整个日记系统也整到上面去 —— 这样就能解决每天通知写日记、自动从相机拍摄的照片中添加文字生成日记,同样的,有了 sqflite,可以很方便的在本地浏览多篇日记而不同每次都请求 HTTP API 了。

总的来说,这一切值得吗?一个熟悉 GUI 框架的前端开发者,基本上都可以在一周左右的时间里熟练掌握 Dart(另一种 TypeScript)和 Flutter(另一种 React)以及常见的组件(类似于 DOM 和 BOM)。但移动端开发的困难才刚刚开始,如何高效的将 WebView 和 FlutterView 在 Android 端融合以实现业务快速更新,如何接入移动支付和地图服务,语音 SDK 和 WebSocket 实现聊天和通知,如何接入微信登录和 Apple ID 登录,如何满足 App Store 苛刻的审核需求开发上架?如何接入 APNS 和国内 Android 通知推送联盟,如何处理一些跨平台 API 的差异问题?

虽然 pub.dev 已经有了大量的库和插件,但 Flutter 的生态相比较 JS 还差不少距离。此外,相比较原生,Flutter 在降低了一部分移动应用开发成本的同时,也相应的损失了一些体验。在 Release 模式下体验一下 Flutter 的动画,对比一下 iOS 的原生动画,尤其是大量消耗内存的页面整页向左滑动切换界面,打开 Drawer 的第一次过渡 —— 差距是明显的,这还是 Flutter 针对性的将 Drawer 层合并布局并且 Release 做了 AOT 后的效果,当然,这也有 iOS 的设计理念和渲染优先级问题。但不管怎样,Flutter 的水平将处于并且长期处于原生和基于 JS 的移动端解决方案之间(更靠近原生)。

而这样的性能表现,也是在舍弃了基于 DOM 和 CSS 低效的多次布局,放弃了动态灵活的 JavaScript 语言而选择一门静态可 AOT 语言之后取得的结果。而真正 Flutter 放弃的大头(做 AOT),则是 Web 灵活性的根本:应用每次打开基本都是动态的从服务器下载到客户端执行。在寡头 App 垄断的当下,引诱用户下载并且安装一款新的 App 的成本不容小觑,更何况是一个不能够动态加载和更新业务的“规则壳子”。至于为什么到现在为止也很少有大型纯 Flutter 应用,反而都是一些大公司的遗留项目的 Flutter 混合开发的原因,则不单单是 Flutter 布道者口中所说的遗留问题,而是遗留项目使用 Flutter 快速迭代是对大公司有利的做法,因为他们可以在自己的巨无霸 App 中灵活的选择使用原生、Flutter 还是 WebView 以最大限度的满足自己的需求,而对于一个小公司而言,纯 Flutter 应用尚且面临着和 WebView 的高性能整合问题,不能小步快跑的空架子哪怕用户交互做的再好也会有极其高昂的维护成本,频繁更新反而降低了用户体验。

因此,从需求的角度来说,Flutter 绝对不会成为主流,但这并不影响如果需要一个长时间使用的、需求固定的应用时,想要提升用户体验的同时不想要移动原生开发的高昂成本时所做出的折中选择,尽管在大部分时候,优先使用 Web 提供 UI 界面是最常见和最正确的选择。而至于 Clojure/Script/Dart 一统三端这种事,细想一下就知道有多不靠谱,一个合格的前端、后端和客户端实现,需要 Clojure 开发者熟练掌握 HTML/CSS、DOM/BOM 的 JavaScript API 以及 React,Redux、Java SE API 和 Java EE API(Spring etc.), Redis, RMDBS 和 SQL、Flutter Widget 和底层布局,更不用提这些技术背后的实现、兼容性和最佳实践。但反而言之,当一个 Java/JavaScript/Dart 开发者拥有了这样的经验和知识,使用 Clojure/Script/Dart 就会有事半功倍的效果。

Clojude to Dart

Clojure to JavaScript

Clojure to Java Bytecode

搜索

    Post Directory