Hello 大家好,我是《Flutter 开发实战详解》的作者,Github GSY 系列开源项目的负责人郭树煜,目前开源的 gsy_github_app_flutter 以 13k 的 star 在中文总榜的 dart 排行上暂处第一名。
image
数据来源: https://github.com/GrowingGit/GitHub-Chinese-Top-Charts/blob/master/content/charts/overall/software/Dart.md
开始之前
Flutter 开源至今其实已经将近 7 年的时间,而我是从 2017 年开始接触的 Flutter ,如今在 2022 年看来,Flutter 已经是不再是以前小众的跨平台框架。
如图所示,可以看到如今的 Flutter 已经有高达 135k
的 star , 10k
Open 和 50k
Closed 的 issue 也足以说明 Flutter 社区和用户的活跃度。
image
在去年下半旬的数据调查中,Flutter 也成为了排名第一的被使用和被喜爱的跨平台框架,这里说这么说并不是说你一定要去学 Flutter ,而是说不管我们喜不喜欢,目前 Flutter 已经证明了它的价值。
image
image.png
数据来源: https://rvtechnologies.com/10-reasons-why-flutter-is-growing-as-a-cross-platform-framework/
其实在去年和前年,我也做过一些简单的统计:
- 2020 年
52
个样本中有19
个 App 里出现了 Flutter; - 2021 年
46
个样本中有24
个 App 里出现了 Flutter;
这份数据样本比较小,主要是从我个人常用的 App 进行统计,所以不准确也不具备代表性,但是可以一定程度反映了国内现在 Flutter 应用使用的情况。
数据来源: https://juejin.cn/post/7012382656578977806
最后 Flutter 在 Web 和 PC 端也有支持,但是我暂时还未投入生产使用,目前可以简单总结就是:
Web
- Flutter Web 目前支持
HtmlCanvas
和CanvasKit
(WASM),默认是移动端使用 HTML 而桌面端使用 WASM; - pub.dev 上
60%
左右的包是 Web 兼容; - 体积和 SEO 是使用过程中最需要提前考虑的问题;
可参考资料: https://juejin.cn/post/7059619009213726733
Desktop
PC 端目前相对更弱势一些,如果是和 Electron
比较,可以简单认为, Flutter PC 版可以使用更低的内存占用和更小的体积,甚至更好的 FFI 继承 C 的能力,但是同样的生态目前也更弱,第三方支持相对较少,需要自己独立解决的问题会相对更多。
Window 可投入生产版本已经正式发布 可参考资料: https://juejin.cn/post/7018450473292136456
Flutter 和原生开发的不同
Flutter 作为跨平台的 UI 框架,它主要的特点是做到:在性能还不错的情况下,框架的 UI 于平台无关,而从平台的角度上看, Flutter 其实就是一个“单页面”的应用。
1、单页面应用
什么是“单页面”应用?
也就是对于原生 Android 和 iOS 而言,整个跨平台 UI 默认都是运行在一个 Activity
/ ViewController
上面,默认情况下只会有一个 Activity
/ ViewController
, 事实上 Flutter、 ReactNative 、Weex 、Ionic 默认情况下都是如此,所以一般情况下框架的路由和原生的路由也是没有直接关系。
举个例子,如下图所示,
- 在当前 Flutter 端路由堆栈里有
FlutterA
和FlutterB
两个页面 Flutter 页面; - 这时候打开新的
Activity
/ViewController
,启动了原生页面X,可以看到原生页面 X 作为新的原生页面加入到原生层路由后,把FlutterActivity
/FlutterViewController
给挡住,也就是把FlutterA
和FlutterB
都挡住; - 这时候在 Flutter 层再打开新的
FlutterC
页面,可以看到依然会被原生页面X挡住;
image
所以通过这部分内容可以看出来,跨平台应用默认情况下作为单页面应用,他们的路由堆栈是和原生层存在不兼容的隔离。
当然这里面重复用了一个词:“默认”,也就是其实可以支持自定义混合堆栈的,比如官方的
FlutterEngineGroup
,第三方框架flutter_boost
、mix_stack
、flutter_thrio
等等都是为了解决混合开发的场景。
2、渲染逻辑
介绍完“单页面”部分的不同,接下来讲讲 Flutter 在渲染层面的不同。
在渲染层面 Flutter 和其他跨平台框架存在较大差异,如下图所示是现阶段常见的渲染模式对比:
image
- 对于原生 Android 而言,是原生代码经过 skia 最后到 GPU 完成渲染绘制,Android 原生系统本身自带了 skia;
- 对于 Flutter 而言,Dart 代码里的控件经过 skia 最后到 GPU 完成渲染绘制,这里在 Andriod 上使用的系统的 skia ,而在 iOS 上使用的是打包到项目里的 skia ;
- 对于 ReactNative/Weex 等类似的项目,它们是运行在各自的 JS 引擎里面,最后通过映射为原生的控件,利用原生的渲染能力进行渲染;( PS,今年官方终于要发布重构的版本了:2022 年 React Native 的全新架构更新 )
- 对于 ionic 等这类 Hybird 的跨平台框架,使用的主要就是 WebView 的渲染能力;
skia 在 Android 上根据不同情况就可能会是
OpenGL
或者Vulkan
,在 iOS 上如果有支持Metal
也会使用Metal
加速渲染。
通过前面的介绍,可以看出了:
ReactNative/Weex
这类跨平台和原生平台存在较大关联:
- 好处就是:如果需要使用原生平台的控件能力,接入成本会比较低;
- 坏处自然就是: 渲染严重依赖平台控件的能力,耦合较多,不同系统之间原生控件的差异,同个系统的不同版本在控件上的属性和效果差异,组合起来在后期开发过程中就是很大的维护成本。
例如:在 iOS 上调试好的样式,在 Android 上出现了异常;在 Android 上生效的样式,在 iOS 上没有支持;在 iOS 平台的控件效果,在 Android 上出现了不一样的展示,比如下拉刷新,Appbar等; 如果这些问题再加上每个系统版本 Framework 的细微差别,就会变得细思极恐。 另外再说个例子,Android 和 iOS 的阴影效果差异。
Flutter
与之不同的地方就是渲染直接利用 skia 和 GPU 交互,在 Android 和 iOS 平台上实现了平台无关的控件,简单说就是 Flutter
里的 Widget
大部分都是和 Android 和 iOS 没有关系。
本质上原生平台是提供一个类似 Surface
的画板,之后剩下的只需要由 Flutter 来渲染出对应的控件
一般是使用
FlutterView
作为渲染承载,它在 Android 上内部使用可以是SurfaceView
、TextureView
或者FlutterImageView
;在 iOS 上是UIView
通过Layer
实现的渲染。
所以 Flutter 的控件在不同平台可以得到一致效果,但是和原生控件进行混合也会有较高的成本和难度,在接入原生控件的能力上,Flutter 提供了 PlatformView
的机制来实现接入, PlatformView
本身的实现会比较容易引发内存和键盘等问题,所以也带来了较高的接入成本。
目前最新版本基本强制要求 Hybrid Composition ,所以相对以前的
PlatformView
会好一点点,当然可能遇到的问题还是有的。比如密码键盘切换,切换页面时PlatformView
时页面闪动。
3、项目结构
image
如上图所示,默认情况下 Flutter 工程结构是这样的:
-
android
原生的工程目录,可以配置原生的appName
,logo
,启动图,AndroidManifest
等等; -
ios
工程目录,配置启动图,logo
,应用名称,plist
文件等等; -
build
目录,这个目录是编译后出现,一般是 git 的 ignore 目录,打包过程和输入结果都在这个目录下,Android 原生的打包过程输出也被重定向输出到这里; -
lib
目录,用来写 dart 代码的,入口文件一般是main.dart
; -
pubspec.yaml
文件,Flutter 工程里最重要的文件之一,不管是静态资源引用(图片,字体)、第三方库依赖还是 Dart 版本声明都写在这里。
如下图是使用是关于 pubspec.yaml
文件的结构介绍
image
需要注意,当这个文件发生改变时,需要重新执行
flutter pub get
,并且stop
应用之后重新运行项目,而不是使用hotload
。
如下所示是 Flutter 的插件工程,Flutter 中分为 Package
和 Plugin
,如果是
-
Package
项目属于 Flutter 包工程,不会包含原生代码; -
Plugin
项目属于 Flutter 插件工程,包含了 Android 和 iOS 代码;
image
4、打包调试
Flutter 运行之前都需要先执行 flutter pub get
来先同步下载第三方代码,下载的第三方代码一般存在于(Mac) /Users/你的用户名/.pub-cache
目录下 。
下载依赖成功后,可以直接通过 flutter run
或者 IDE 工具点击运行来启动 Flutter 项目,这个过程会需要原生工程的一些网络同步工作,比如:
- Android 上的 Gradle 和 aar 依赖包同步;
- iOS 上需要 pod install 同步一些依赖包;
如果需要在项目同步过程中查看进度:
- Android 可以到
android/
目录下执行./gradlew assembleDebug
查看同步进度; - iOS 可以到
ios/
目录下执行pod install
,查看下载进度;
同步的插件中,如果是 Plugin
带有原生平台的代码逻辑,那么可以在项目根目录下看到一个叫做 .flutter_plugins
和 .flutter-plugins-dependencies
的文件,它们是 git ignore 的文件,Android 和 iOS 中会根据这个文件对本地路径的插件进行引用,后面 Flutter 运行时会根据这个路径动态添加依赖。
image
默认情况下 Flutter 在 debug 下是 JIT 的运行模式所以运行效率会比较低,速度相对较慢,但是可以 hotload。
在 release 下是 AOT 模式,运行速度会快很多,同时 Flutter 在模拟器上一般默认会使用 CPU 运行,在真机上会使用 GPU 运行,所以性能表现也不同。
另外 iOS 14 真机上 debug 运行,断后链接后再次启动是无法运行的。
如果项目存在缓存问题,可以直接执行 flutter clean
来清理缓存。
最后说下 Flutter 的为什么不支持热更新?
前面讲过 ReactNative 和 Weex 是通过将 JS 代码里的控件转化为原生控件进行渲染,所以本质上 JS 代码部分都只是文本而已,利用 code-push
推送文本内容本质上并不会违法平台要求。
而 Flutter 打包后的文件是二进制文件,推送二进制文件明显是不符合平台要求的。
release 打包后的 Android 会生成
app.so
和flutter.so
两个动态库;iOS 会生成App.framework
和Flutter.framework
两个文件。
所以 Flutter 的第三方热更新市面上常见的有:MxFlutter
、Fair
、Kraken
、liteApp
、NEJFlutter
、Flap
(MTFlutter)、flutter_code_push
(chimera) 等等,而这些框架都不会是直接下发可执行的二进制文件,大致市面上根据 DSL 的不同,动态化方案可以分为两大类:面向前端的和面向终端。
如下图所示,例如 WXG 的 LiteApp
、腾讯的 MxFlutter
和阿里的 Kraken
(北海) 就是面向前端 ,使用 JS/TS 。
image
参考资料: https://mp.weixin.qq.com/s/OpgqjTIiB6z9YiN1FTDeYQ
如下图所示:例如 Flap
、flutter_code_push
就是面向终端,主要是对 Dart 的 DSL 或者编码下功夫。
image
参考资料: https://tech.meituan.com/2020/06/23/meituan-flutter-flap.html
最后,关于 Flutter 热更新动态化的支持,可以参考这个表格:
image
参考资料:Flutter实现动态化更新-技术预研 https://juejin.cn/post/7033708048321347615
5、Flutter 简单介绍
这里介绍下 Flutter Dart 部分相关的内容,对于原生开发来说,Flutter 主要优先了解响应式和Widget
。
响应式
响应式编程也叫做声明式编程,这是现在前端开发的主流,当然对于客户端开发的一种趋势,比如 Jetpack Compose
、SwiftUI
。
Jetpack Compose 和 Flutter 的在某些表层上看真的很相似。
响应式简单来说其实就是你不需要手动更新界面,只需要把界面通过代码“声明”好,然后把数据和界面的关系接好,数据更新了界面自然就更新了。
从代码层面看,对于原生开发而言,没有 xml
的布局,没有 storyboard
,布局完全由代码完成,所见即所得,同时也不会需要操作界面“对象”去进行赋值和更新,你所需要做的就是配置数据和界面的关系。
响应式开发比数据绑定或者 MVVM 不同的地方是,它每次都是重新构建和调整整个渲染树,而不是简单的对 UI 进行
visibility
操作。
如下图所示,是 Flutter 下针对响应式 UI 的典型第三方示例: responsive_framework
image
image
Widget
Widget
是 Flutter 里的基础概念,也是我们写代码最直接接触的对象,Flutter 内一切皆 Widget ,Widget 是不可变的(immutable),每个 Widget 状态都代表了一帧。
所以 Widget
作为一个 immutable
对象,它不可能是真正工作的 UI 对象,在 Flutter 里真正的 View
级别对象是 Element
和 RenderObject
, 其中 Element
的抽象对象就是我们经常用到的 BuildContext
。
举个例子,如下代码所示,其中 testUseAll
这个 Text
在同一个页面下在三处地方被使用,并且代码可以正常运行渲染,如果是一个真正的 View
,是不能在一个页面下这样被多个地方加载使用的。
image
所以 Flutter 中 Widget
更多只是配置文件的地位,用于描述界面的配置代码,具体它们的实现逻辑、关系还有分类,可以看我写的书 《Flutter开发实战详解》中 的第三章和第四章部分。
有趣的问题
最后说一个比较有意思的问题,之前有人说 Flutter 里是传递值还是引用?这个问题看过网上有不少文章解释得很奇怪,存在一些误导性的解释,其实这个问题很简单:
Flutter 里一切皆是对象, 就连 int
、 double
、bool
也是对象,你觉得对象传递的是什么?
但是对于对象的操作是有区别的,比如对于 int
、 double
等 class
的
、-
、*
、 等操作,其实是执行了这个
class
的 operator
操作符的操作, 然后返回了一个 num
对象。
image
而对于这个操作,只需要要去 dart vm
看看 Double
对象在进行加减乘除时做了什么,如下图所示,看完相信就知道方法里传递 int
、double
对象后进行操作会是什么样的结果。
image
Flutter 和 Compose
最后聊一聊 Flutter 和 Compose。
其实自从 Jetpack Compose 面世以来,关于 Flutter 与 Compose 之间的选择问题就开始在 Android 开发中出现,就如同之前有 iOSer 纠结在 Flutter 和 SwiftUI 之间选谁一样,对于 Android 开发来说似乎“更头痛”的是 Flutter 与 Compose “同出一爹”。
这里我只是提供一些我个人的理解,并不代表官方的观点:
Flutter 和 Compose 的未来目标会比较一致,但是至少它们出现的初衷是不一样。
首先 Compose 是 Jetpack 系列的全新 UI 库,理解下这点!Compose 是 Jetpack 系列的成员之一,所以可以被应用到 Android 界面开发中,所以你也可以选择不用,用不用都能开发 Android 的 UI 。
然后再说 Compose 出生的目的:就是为了重新定义 Android 上 UI 的编写方式,为了提高 Android 原生的 UI 开发效率,让 Android 的 UI 开发方式能跟上时代的步伐。
不管你喜不喜欢,声明式的界面开发就是如今的潮流,不管是 React 、SwiftUI 、Flutter 等都在表明这一点。
而对于 Flutter 而言就是跨平台,因为 Flutter 没有自己的平台 ,有人说 Fuchsia
会是 Flutter 的家,但那已经属于后话,毕竟 Fuchsia
要先能养活自己。
因为 Flutter 出生就是为了跨平台存在的全新 UI 框架,从底层到上层都是“创新”和“大胆”的设计,就选择 Dart 本身就是一项很“大胆”的决定,甚至在 Web 平台都敢支持选用 Canvaskit
的 WASM
模式。
所以 Flutter 的“任性”从一出来就不被看好,当然至今也有不看好它的人,因为它某种程度很“偏激”和不友好。
另外从起源和维护上:
- Flutter 起源是 Chrome 项目组,选用了 Dart ,所以 Flutter 并不是归属于 Android 的项目;
- Compose 起源于 Android 团队,它使用的是 Kotlin ;
所以他们起源和维护都属于不同 Group ,所以从我们外界看可能会觉得有资源冲突,但是本质上他们是不同的大组在维护的。
好了,扯了那么多,总结下就是:
- Compose 是 Android UI 的未来,现阶段你可以不会,但是如果未来你会继续在 Android 平台的话,你就必须会。 ,而 Compose 的跨平台支持也在推进,不过不是谷歌维护,而是由 Jetpack 提供的 Compose for Compose Multiplatform 。
- Flutter 的未来在于多平台,更稳定可靠的多平台 UI 框架。如果你的路线方向不是大前端或者多端开发者,那你可以不会也没关系。
说带了这些框架主要还是做 UI 的,学哪个看你喜欢哪个就行~当然,可能更重要是看你领导要求你用哪个,而回归到冲突的问题上, Flutter 和 Compose 冲突吗?
从立项的意义上看 Flutter 和 Compose 好像是冲突的,但是从使用者的角度看,它们并不冲突。
因为对于开发者而言,不管你是先学会 Compose 还是先学会 Flutter,对于你掌握另外一项技能都有帮助,相当于学会一种就等于学会另一种的 70%
从未来的角度看:
- 如果你是原生开发,还没接触过 Flutter , 那先去学 Compose ,这对你的 Android 生涯更有帮助,然后再学 Flutter 也不难。
- 如果你已经在使用或者学习 Flutter ,那么请继续深造,不必因为担心 Compose 而停滞不前,当你掌握了 Flutter 后其实离 Compose 也不远了。
它们二者的未来都会是多平台,而我认为的冲突主要是在于动手学起来,而不是在二者之间徘徊纠结。
从现实角度出发:目前 Flutter 2.0 下的 Android 和 iOS 已经趋向稳定,Web 已经进入 Stable 分支,而 Macos/Linux/Win 也进入了 Beta 阶段,并且可以在 Stable 分支通过 snapshot 预览。所以从这个阶段考虑,如果你需要跨平台开发,甚至 PC 平台,那么优先考虑 Flutter 吧。
你选择 React Native 也没问题,说起来最近 React Native 的版本号已经到了 0.67 了,还是突破不到 1.0 ····
当然大家可能会关心框架是否有坑的问题,本质上所有框架都有坑,甚至网络因素都可能会成为你的痛点,问题在于你是否接受这些坑,平台的背后本身就是“脏活”和“累活”, Flutter 的全平台之路很艰难,能做好 Android 和 iOS 的支持和兼容就很不容易了。
最后还是要例行补充这一点:
跨平台之所以是跨平台,首先就是要有对应原生平台的存在, 很多原生平台的问题都需要回归到平台去解决,那些喜欢吹 xxx 制霸原生要凉的节奏,仅仅是因为“你的焦虑会成为它们的利润”。