开发随笔

Flutter / Compose / MAUI 开发体验:基于实际项目的取舍

结合 Flutter 跨版本项目、Android Studio 下的 Jetpack Compose 界面开发与 .NET MAUI 尝试,围绕 UI 编写、IDE 预览、热重载与插件维护记录实际体验。

约 7 分钟读完
目录
  1. 1. UI 声明方式与多端一致性
  2. 2. IDE 可视化预览 vs 热重载反馈
  3. 3. 大版本升级与第三方插件维护成本
  4. 4. 一点个人观察与选型边界

这几年在做个人工具与客户端项目时,先后用到了 Flutter、Jetpack Compose(含 Compose Multiplatform)和 .NET MAUI。不同框架在宣传文档里往往都强调声明式开发与多端复用,但真正落到每天写布局、调样式、改状态和升版本的日常开发里,实际体验的差异还是比较明显的。

为了避免脱离场景空谈优劣,先把我实际使用这三套技术栈的任务范围交代清楚:

  • Flutter:主力使用,覆盖带原生音视频桥接的移动端项目、端侧 TFLite 图像超分工具,以及近期基于 flutter_scene 的三维渲染实验(经历了从 Flutter 3.22 / Dart 3.4 向 Dart 3.5+ 升级的阶段)。
  • Jetpack Compose:在 Android Studio 下编写 Android 原生业务列表、缓存分页与设置界面,并少量尝试了 Compose Multiplatform 桌面端页面。
  • .NET MAUI:在 Visual Studio 下尝试构建桌面与移动端共用的常规表单、列表页面(非重度商业长线项目)。

下面围绕日常接触最多的四个具体环节做一次对照记录。

1. UI 声明方式与多端一致性

Flutter 和 Compose 都采用了纯代码声明 UI 的方式(Compose 在 Android 工程里除了桌面微件、应用清单与部分系统资源外,常规页面已基本告别 XML)。在嵌套较深的状态分支、条件渲染和动画组合上,直接用宿主语言写组件比在模板文件与逻辑代码之间来回跳转顺手得多。两者的区别主要在渲染边界:Flutter 通过自有引擎(Skia / Impeller)接管绘制,跨平台像素表现高度统一,但与原生视图(PlatformView)混排时需要额外留心生命周期;Compose 在 Android 上直接融入原生视图体系,与既有原生 SDK 协作更自然。

.NET MAUI 则延续了 Xamarin.Forms 的 XAML 布局加 C# 逻辑绑定模式。如果原本熟悉 WPF 那套属性绑定与资源字典,上手并不算陌生;但相比直接在 Dart 或 Kotlin 函数里组合组件,写一套列表项和状态联动需要铺垫的样板代码依然偏多。

2. IDE 可视化预览 vs 热重载反馈

这是三套工具链日常使用体验差异最大的地方:

  • Jetpack Compose 的强项在静态与交互式 @Preview:因为语言编译器(Kotlin)、UI 框架与 IDE(Android Studio / Fleet)都在 JetBrains 与 Google 的协同链条内,Android Studio 能直接解析 @Preview 注解并单独渲染某个 Composable,支持同时并排查看不同屏幕宽度、深色模式与系统字号缩放下的排版效果。调卡片边距、两行标题截断这类局部视觉细节时非常省心。
  • Flutter 用 Stateful Hot Reload 弥补了静态预览的不足:由于 Flutter 走独立渲染管线,Android Studio 很难像渲染原生布局那样直接拖拽编辑单个 Widget。但在实际项目里,这一点基本被稳定的热重载抵消了——保留当前页面状态和接口数据的前提下,改完参数保存,真机画面在一秒内就能刷新,对独立预览器的依赖其实没有想象中那么高。
  • .NET MAUI 当时影响体验最明显的是热重载稳定性:在我尝试的版本里,只改文字颜色、字号这类简单属性时 XAML Hot Reload 还能生效;一旦涉及增删布局节点或调整容器层级,就经常遇到热重载断连、已删掉的控件在画面上残留一块空白,或者直接弹窗提示当前编辑需要重新编译运行。一轮界面微调下来,被打断的次数明显偏多。

3. 大版本升级与第三方插件维护成本

跳出单纯写 UI 的顺畅度,长期维护一个跨端工程更考验外围依赖的跟进速度。这一点上我在将旧 Flutter 项目从 3.22.3(Dart 3.5 之前)往上升级时遇到过几处明显的兼容断层:

  1. 相册保存类插件停更:项目原本依赖的 image_gallery_saver 近两年未发新版,升级 AGP 8 与新版 Flutter 后直接因为 namespace 与接口兼容问题无法编译;最后是翻仓库 issue #37 才找到社区复刻维护的兼容替代包(如 image_gallery_saver_plus),调用逻辑没变,但需要手动替换依赖与方法名。
  2. 偏底层垂直领域的官方包跟进滞后:在做端侧超分时用到的 tflite_flutter,有一段时间对新版 Dart FFI 与构建配置的适配明显滞后,而这类涉及原生 C API 绑定的包不像纯 UI 组件那样容易随手重写,短期内很难找到成熟替代品。

这也是使用 Flutter 时很现实的一体两面:pub.dev 上的纯 Dart UI 库非常丰富,但一旦项目深入到相机流、蓝牙、端侧推理或特定系统能力,只要底层还是靠社区单人维护的 MethodChannel / FFI 插件桥接,每次跨大版本升级 Android Gradle Plugin 或 Dart SDK 前都得先盘点一遍插件仓的活跃度。相比之下,如果应用本身只面向 Android 单平台,直接用 Jetpack Compose 调原生 SDK,就少了一层跨端插件失修的顾虑。

4. 一点个人观察与选型边界

前面几点都是具体项目里碰到的事实;往外延展一点,纯属我个人的主观印象:这几年能感觉到 Google 移动端的一部分重心在向原生 Compose 倾斜,社区里也因为对主仓库合入节奏的讨论衍生出过 Flock 分支。早期维基百科词条里提到 Dart “目标在于成为下一代结构化 Web 开发语言”,如今它的主力阵地其实还是由 Flutter 撑起的跨平台客户端。

如果按我目前的实际取舍来划分:

  • 需要一套代码同时兼顾 Android / iOS 乃至自定义图形渲染,或者很看重极快的状态保持热重载时,我依然会首选 Flutter,但对重度依赖原生能力的模块倾向于自己写薄桥接,尽量减少对无人维护的第三方封装包的强依赖,但是也会出现反复造轮子的问题。
  • 纯 Android 项目、或原生页面占比极高的工程,Jetpack Compose 在工具链预览和原生 API 零隔阂调用上更稳妥;
  • .NET MAUI 除非团队已有深厚的 C# / XAML 历史资产需要复用,否则单从 UI 调试反馈来看暂时不会作为我的优先选项。

(至于 React Native 等基于前端生态的跨端方案,因为我平时没有在实际项目里深入写过,就不在这里凭二手印象凑对比了,留待以后真正做过项目再聊。)