原生开发如何学习 Flutter | 谷歌社区说

4,696 阅读16分钟

Hello 大家好,我是《Flutter 开发实战详解》的作者,Github GSY 系列开源项目的负责人郭树煜,目前开源的 gsy_github_app_flutter 以 13k+ 的 star 在中文总榜的 dart 排行上暂处第一名。

数据来源: github.com/GrowingGit/…

开始之前

Flutter 开源至今其实已经将近 7 年的时间,而我是从 2017 年开始接触的 Flutter ,如今在 2022 年看来,Flutter 已经是不再是以前小众的跨平台框架

如图所示,可以看到如今的 Flutter 已经有高达 135k 的 star , 10k+ Open 和 50k+ Closed 的 issue 也足以说明 Flutter 社区和用户的活跃度。

在去年下半旬的数据调查中,Flutter 也成为了排名第一的被使用和被喜爱的跨平台框架,这里说这么说并不是说你一定要去学 Flutter ,而是说不管我们喜不喜欢,目前 Flutter 已经证明了它的价值。

image.png

数据来源: rvtechnologies.com/10-reasons-…

其实在去年和前年,我也做过一些简单的统计:

  • 2020 年 52 个样本中有 19 个 App 里出现了 Flutter;
  • 2021 年 46 个样本中有 24 个 App 里出现了 Flutter;

这份数据样本比较小,主要是从我个人常用的 App 进行统计,所以不准确也不具备代表性,但是可以一定程度反映了国内现在 Flutter 应用使用的情况。

数据来源: juejin.cn/post/701238…

最后 Flutter 在 Web 和 PC 端也有支持,但是我暂时还未投入生产使用,目前可以简单总结就是:

Web

  • Flutter Web 目前支持 HtmlCanvasCanvasKit(WASM),默认是移动端使用 HTML 而桌面端使用 WASM;
  • pub.dev 上 60% 左右的包是 Web 兼容;
  • 体积和 SEO 是使用过程中最需要提前考虑的问题;

可参考资料: juejin.cn/post/705961…

Desktop

PC 端目前相对更弱势一些,如果是和 Electron 比较,可以简单认为, Flutter PC 版可以使用更低的内存占用和更小的体积,甚至更好的 FFI 继承 C 的能力,但是同样的生态目前也更弱,第三方支持相对较少,需要自己独立解决的问题会相对更多。

Window 可投入生产版本已经正式发布

可参考资料: juejin.cn/post/701845…

Flutter 和原生开发的不同

Flutter 作为跨平台的 UI 框架,它主要的特点是做到:在性能还不错的情况下,框架的 UI 与平台无关,而从平台的角度上看, Flutter 其实就是一个“单页面”的应用。

1、单页面应用

什么是“单页面”应用?

也就是对于原生 Android 和 iOS 而言,整个跨平台 UI 默认都是运行在一个 Activity / ViewController 上面,默认情况下只会有一个 Activity / ViewController, 事实上 Flutter、 ReactNative 、Weex 、Ionic 默认情况下都是如此,所以一般情况下框架的路由和原生的路由也是没有直接关系

举个例子,如下图所示,

  • 在当前 Flutter 端路由堆栈里有 FlutterAFlutterB 两个页面 Flutter 页面;
  • 这时候打开新的 Activity / ViewController,启动了原生页面X,可以看到原生页面 X 作为新的原生页面加入到原生层路由后,把 FlutterActivity / FlutterViewController 给挡住,也就是把 FlutterAFlutterB 都挡住;
  • 这时候在 Flutter 层再打开新的 FlutterC 页面,可以看到依然会被原生页面X挡住;

所以通过这部分内容可以看出来,跨平台应用默认情况下作为单页面应用,他们的路由堆栈是和原生层存在不兼容的隔离

当然这里面重复用了一个词:“默认”,也就是其实可以支持自定义混合堆栈的,比如官方的 FlutterEngineGroup ,第三方框架 flutter_boostmix_stackflutter_thrio 等等都是为了解决混合开发的场景。

2、渲染逻辑

介绍完“单页面”部分的不同,接下来讲讲 Flutter 在渲染层面的不同。

在渲染层面 Flutter 和其他跨平台框架存在较大差异,如下图所示是现阶段常见的渲染模式对比:

  • 对于原生 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 上内部使用可以是 SurfaceViewTextureView 或者 FlutterImageView ;在 iOS 上是 UIView 通过 Layer 实现的渲染。

所以 Flutter 的控件在不同平台可以得到一致效果,但是和原生控件进行混合也会有较高的成本和难度,在接入原生控件的能力上,Flutter 提供了 PlatformView 的机制来实现接入, PlatformView 本身的实现会比较容易引发内存和键盘等问题,所以也带来了较高的接入成本。

目前最新版本基本强制要求 Hybrid Composition ,所以相对以前的 PlatformView 会好一点点,当然可能遇到的问题还是有的。比如密码键盘切换,切换页面时 PlatformView 时页面闪动。

3、项目结构

如上图所示,默认情况下 Flutter 工程结构是这样的:

  • android 原生的工程目录,可以配置原生的 appNamelogo ,启动图, AndroidManifest 等等;
  • ios 工程目录,配置启动图,logo,应用名称,plist 文件等等;
  • build 目录,这个目录是编译后出现,一般是 git 的 ignore 目录,打包过程和输入结果都在这个目录下,Android 原生的打包过程输出也被重定向输出到这里;
  • lib 目录,用来写 dart 代码的,入口文件一般是 main.dart
  • pubspec.yaml 文件,Flutter 工程里最重要的文件之一,不管是静态资源引用(图片,字体)、第三方库依赖还是 Dart 版本声明都写在这里。

如下图是使用是关于 pubspec.yaml 文件的结构介绍

需要注意,当这个文件发生改变时,需要重新执行 flutter pub get,并且 stop 应用之后重新运行项目,而不是使用 hotload

如下所示是 Flutter 的插件工程,Flutter 中分为 PackagePlugin ,如果是

  • Package 项目属于 Flutter 包工程,不会包含原生代码;
  • Plugin 项目属于 Flutter 插件工程,包含了 Android 和 iOS 代码;

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 运行时会根据这个路径动态添加依赖。

默认情况下 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.soflutter.so 两个动态库;iOS 会生成 App.frameworkFlutter.framework 两个文件。

所以 Flutter 的第三方热更新市面上常见的有:MxFlutterFairKrakenliteAppNEJFlutterFlap(MTFlutter)、flutter_code_push (chimera) 等等,而这些框架都不会是直接下发可执行的二进制文件,大致市面上根据 DSL 的不同,动态化方案可以分为两大类:面向前端的和面向终端。

如下图所示,例如 WXG 的 LiteApp、腾讯的 MxFlutter 和阿里的 Kraken (北海) 就是面向前端 ,使用 JS/TS 。

参考资料: mp.weixin.qq.com/s/OpgqjTIiB…

如下图所示:例如 Flapflutter_code_push 就是面向终端,主要是对 Dart 的 DSL 或者编码下功夫。

参考资料: tech.meituan.com/2020/06/23/…

最后,关于 Flutter 热更新动态化的支持,可以参考这个表格:

参考资料:Flutter实现动态化更新-技术预研 juejin.cn/post/703370…

5、Flutter 简单介绍

这里介绍下 Flutter Dart 部分相关的内容,对于原生开发来说,Flutter 主要优先了解响应式和Widget

响应式

响应式编程也叫做声明式编程,这是现在前端开发的主流,当然对于客户端开发的一种趋势,比如 Jetpack ComposeSwiftUI

Jetpack Compose 和 Flutter 的在某些表层上看真的很相似。

响应式简单来说其实就是你不需要手动更新界面,只需要把界面通过代码“声明”好,然后把数据和界面的关系接好,数据更新了界面自然就更新了。

从代码层面看,对于原生开发而言,没有 xml 的布局,没有 storyboard,布局完全由代码完成,所见即所得,同时也不会需要操作界面“对象”去进行赋值和更新,你所需要做的就是配置数据和界面的关系

响应式开发比数据绑定或者 MVVM 不同的地方是,它每次都是重新构建和调整整个渲染树,而不是简单的对 UI 进行 visibility 操作。

如下图所示,是 Flutter 下针对响应式 UI 的典型第三方示例: responsive_framework

Widget

Widget 是 Flutter 里的基础概念,也是我们写代码最直接接触的对象,Flutter 内一切皆 Widget ,Widget 是不可变的(immutable),每个 Widget 状态都代表了一帧。

所以 Widget 作为一个 immutable 对象,它不可能是真正工作的 UI 对象,在 Flutter 里真正的 View 级别对象是 ElementRenderObject , 其中 Element 的抽象对象就是我们经常用到的 BuildContext

举个例子,如下代码所示,其中 testUseAll 这个 Text 在同一个页面下在三处地方被使用,并且代码可以正常运行渲染,如果是一个真正的 View ,是不能在一个页面下这样被多个地方加载使用的。

所以 Flutter 中 Widget 更多只是配置文件的地位,用于描述界面的配置代码,具体它们的实现逻辑、关系还有分类,可以看我写的书 《Flutter开发实战详解》中 的第三章和第四章部分。

有趣的问题

最后说一个比较有意思的问题,之前有人说 Flutter 里是传递值还是引用?这个问题看过网上有不少文章解释得很奇怪,存在一些误导性的解释,其实这个问题很简单:

Flutter 里一切皆是对象, 就连 intdoublebool 也是对象,你觉得对象传递的是什么?

但是对于对象的操作是有区别的,比如对于 intdoubleclass+-*\ 等操作,其实是执行了这个 classoperator 操作符的操作, 然后返回了一个 num 对象。

而对于这个操作,只需要要去 dart vm 看看 Double 对象在进行加减乘除时做了什么,如下图所示,看完相信就知道方法里传递 intdouble 对象后进行操作会是什么样的结果。

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 平台都敢支持选用 CanvaskitWASM 模式。

所以 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 制霸原生要凉的节奏,仅仅是因为“你的焦虑会成为它们的利润”。