Flutter 3.29 正式发布,如果不出意外,这将是一个带着“大坑”到来的版本,因为该版本带来了很多「重大调整或者弃用」 ,所以如果你想升级到 3.29,还需要多慎重了解这次升级到底更新了什么

之所以说带着“大坑”,主要是 3.29 更新带来了太多“意料之外”的东西,例如:

  • Dart 代码会直接在 Android/iOS 的主 UI 线程上运行,而不是单独的 Dart UI 线程,此时 Dart 和平台调用直接可以同步执行

  • iOS skia 正式被移除

  • 没有 Vulkan 驱动的 Android 设备将回退到在 OpenGLES 上运行的 Impeller,而不是使用 Skia

  • 移除了 Flutter Gradle 插件,之前没迁移的需要手动迁移适配

  • Web 平台 HTML renderer 正式移除

  • 全新的 DevTools

相信大家从上述描述里应该可以感受到 3.29 潜在的“威能”,那么下面就让我们看看 3.29 给我们带来了什么更新吧。

Framework Cupertino 更新

和 3.27 那会一样, Flutter iOS 的 PM 回归后, 每次更新都会包含不少 Cupertino 的身影,而本次 3.29 开始CupertinoNavigationBarCupertinoSliverNavigationBar将支持 bottom widget 配置,一般会用于搜索字段或分段控件的场景。

例如在CupertinoSliverNavigationBar里现在可以使用 bottomMode 属性配置底部 Widget,从而支持自动调整大小直到隐藏,或者始终在导航栏滚动时显示:

打开网易新闻 查看更多图片

打开网易新闻 查看更多图片

❝ Flutter 在 Cupertino 的高保真支持上力度上,从近来的几个版本都可以明显感受到。

其他导航栏的更新包括:

  • 当部分滚动时,CupertinoSliverNavigationBar可以在展开和折叠状态之间对齐

  • 新的CupertinoNavigationBar.large构造函数支持静态导航栏显示大标题

  • Cupertino popups 窗口支持更生动的背景模糊效果,从而提高了 Cupertino 风格的保真度:

    打开网易新闻 查看更多图片

    新的CupertinoSheetRoute可以使用拖动手势将其移除,同时还提供了新的showCupertinoSheet

    打开网易新闻 查看更多图片

  • CupertinoAlertDialog改进了在深色模式下的 native 保真度:

    打开网易新闻 查看更多图片

  • 在文本选择时,反转选择后,Flutter 的文本选择手柄在 iOS 上会交换它们的顺序,并且文本选择放大镜的边框颜色现在会和当前主题匹配:

    打开网易新闻 查看更多图片

❝ 不得不说最近几个版本都有关于文本选择的更新内容,也许不久之后,Flutter 文本处理能力的短板会被补齐。
Material 更新

谷歌对于 Material 设计规范的跟进依然还是「迷之聚焦」,这对于国内开发者而言不知是福是祸, 3.29 提供了新的 Material 3 页面过渡构建器FadeForwardsPageTransitionsBuilder,主要是为了匹配 Android 的最新页面过渡行为。

在页面跳转过渡期间,页面会从右向左滑动淡入,退出页面会从右向左滑动淡出,这个新过渡还解决了以前由ZoomPageTransitionsBuilder导致的性能问题:

打开网易新闻 查看更多图片

此外,3.29 还更新了CircularProgressIndicatorLinearProgressIndicator,从而适配 Material 3 规范,如果要使用更新的样式,需要将year2023属性设置为false,或者将 ProgressIndicatorThemeData.year2023设置为false

打开网易新闻 查看更多图片

打开网易新闻 查看更多图片

❝ year2023 属于将 Deprecated 的字段。

在 3.29 还引入了 M3 最新的Slider设计样式,Slider默认为以前的 Material 3 样式,如果要启用最新设计,同样需要将year2023设置为false,或将SliderThemeData.year2023设置为false

打开网易新闻 查看更多图片
最后,3.29 里还包含了 Material library 的多个错误修复和功能增强,例如:

  • Keyboard navigation 现在可以正确触发DropdownMenu.onSelected回调

  • 改进了TabBar弹性动画

    打开网易新闻 查看更多图片

  • 改进了RangeSlider的 thumb 对齐方式,包括 divisions 、padding 和圆角

  • 增强了多个 Material 组件的自定义支持,例如mouseCursor属性已添加到ChipTooltipReorderableListView里面,从而允许在悬停时自定义鼠标光标

Text selection

近来几个版本 Framework 更新都会包含文本选择的更新,比如之前各种手势和鼠标触发的选择效果等,而 3.29 现在通过SelectionListenerSelectionListenerNotifier提供了有关SelectionAreaSelectableRegion下的文本选择信息:

SelectionDetails(通过SelectionListenerNotifier获得)提供了所选内容的开始和结束偏移量(相对于 wrapped 的子树),并提示所选内容是否存在以及是否已折叠:

打开网易新闻 查看更多图片

另外还可以通过SelectableRegionSelectionStatusScope获取SelectionAreaSelectableRegion状态的信息,例如通过使用SelectableRegionSelectionStatusScope.maybeOf(context)检查SelectableRegionSelectionStatus状态:

class MySelectableText extends StatefulWidget {   const MySelectableText({super.key, required this.selectionNotifier, required this.onChanged});   final SelectionListenerNotifier selectionNotifier;   final ValueChanged onChanged;   @override   State createState() => _MySelectableTextState(); } class _MySelectableTextState extends State

  {   ValueListenable ? _selectableRegionScope;   void _handleOnSelectableRegionChanged() {     if (_selectableRegionScope == null) {       return;     }     widget.onChanged.call(_selectableRegionScope!.value);   }   @override   void didChangeDependencies() {     super.didChangeDependencies();     _selectableRegionScope?.removeListener(_handleOnSelectableRegionChanged);     _selectableRegionScope = SelectableRegionSelectionStatusScope.maybeOf(context);     _selectableRegionScope?.addListener(_handleOnSelectableRegionChanged);   }   @override   void dispose() {     _selectableRegionScope?.removeListener(_handleOnSelectableRegionChanged);     _selectableRegionScope = null;     super.dispose();   }   @override   Widget build(BuildContext context) {     return SelectionListener(       selectionNotifier: widget.selectionNotifier,       child: const Text('This is some text under a SelectionArea that can be selected.'),     );   } }
Accessibility

3.29 改进了多个 Material 控件里 Accessibility 的用户体验:

  • 启用屏幕阅读器后,表单 Widget 仅宣布它遇到的第一个错误。

  • 屏幕阅读器现在会读出下拉菜单的正确标签。

Web

在去年发布 wasm 时,Flutter 在 Web 上的 WebAssembly (wasm) 支持要求开发者使用特殊的 HTTP 响应 headers 来托管 Flutter 应用,而现在相关要求在 3.29 开始放宽。

现在使用默认 headers 可以允许应用使用 wasm 运行,但仅限于单个线程,而更新 headers 可以让 wasm 构建的 Flutter Web 应用使用多个线程运行。

同时 3.29 修复了 WebGL 后端上图像的几个问题:

  • 从 UI 线程异步解码图像以避免卡顿

  • Image.network 开箱即用支持 CORS 图像

另外补充一点,关于 Flutter Web 在 WebAssembly 上的 SEO 优化支持,官方正在研究一种使用Flutter 语义树来适配的场景

打开网易新闻 查看更多图片

Flutter Web 在 3.29 还允许开发者更好地控制图像在 Web 上的显示方式,过去 Image 会在发生 CORS 错误时自动使用元素来显示来自 URL 的图像,这可能会导致出现一些不一致的行为。

而现在webHtmlElementStrategy标志允许开发者选择何时使用元素,虽然默认情况下自动回退处于禁用状态,不过仍然可以标志启用回退,甚至根据应用场景确定元素的优先级。

打开网易新闻 查看更多图片
Engine Impeller Vulkan 稳定性

3.29 修复了不少 Vulkan 问题,包活:

  • 修复了许多用户在支持 Vulkan 的旧版设备上出现的可重现闪烁和视觉抖动问题

  • 禁用了 Android Hardware Buffer swapchains

  • 由于在 MediaTek/PowerVR SoC 上使用 Vulkan 会导致的大量黑屏和崩溃报告,目前这些设备现在仅使用 Impeller OpenGLES,注意不是 skia

  • Android 模拟器已更新为使用 Impeller GLES 后端,注意不是 skia

Impeller OpenGLES

在 3.29 里,没有 Vulkan 驱动的 Android 设备将回退到在 OpenGLES 上运行的 Impeller,而不是使用 Skia,默认情况下该行为处于启用状态,无需配置。

❝ 这样 Flutter 将实现 100% 支持 Android 上的 Impeller。
Impeller iOS

和去年提到的 roadmap 一样,现在 iOS 后端已删除 Skia 支持,并且FLTEnableImpeller选择退出标志不再有效,随着 Flutter 开始从 iOS 版本中删除 Skia ,预计在未来版本中会进一步减小二进制文件大小。

新功能 Backdrop filter优化

从 3.29 开始,显示多个背景滤镜的应用现在可以使用新的BackdropGroup和新的BackdropFilter.grouped,通过这些 Widget 可以提高多个模糊的性能:

Widget build(BuildContext context) {   return BackdropGroup(     child: ListView.builder(       itemCount: 60,       itemBuilder: (BuildContext context, int index) {         return ClipRect(           child: BackdropFilter.grouped(             filter: ui.ImageFilter.blur(               sigmaX: 40,               sigmaY: 40,             ),             child: Container(               color: Colors.black.withOpacity(0.2),               height: 200,               child: const Text('Blur item'),             ),           ),         );      }    ),  );
❝ 上述代码展示了如何使用 BackdropGroup 让每个列表项都有一个有效的模糊效果,并且引擎将只执行一次背景模糊,但结果在视觉上与多次模糊相同。

在 3.29 里,如果这些 Backdrop filter 控件都共享一个共同的 BackdropKey,那么 Flutter 引擎就可以将多个背景过滤器组合到一个渲染操作中

Backdrop Key 可以唯一标识背景过滤器的输入,当共享时表示执行一次过滤,这可以显著减少在场景中使用多个背景滤镜的开销。

对应的 Key 可以通过backdropKey构造函数参数手动提供,也可以通过.grouped构造函数从[BackdropGroup] 中查找。

ImageFilter.shader

新的ImageFilter构造函数允许将自定义着色器应用于任何子 Widget ,这和package:flutter_shaders中的AnimatedSampler功能类似,不同之处在于它还适用于 backdrop filters 。

Android/iOS 上的 Dart 线程更改

众所周知,一直以来Flutter 的 Dart UI Thread 和 Android/iOS 平台的 UI Thread 是不同线程,这在 和过去我们聊 background isolate 得时候聊过,而独立的 Dart UI 线程的主要目的之一防止阻塞平台 UI 线程。

但是由于 Flutter 是在与 native 主线程不同的 Thread(UI 线程)上执行 Dart 代码,所以会出现 Dart 和平台互相调用时需要序列化和异步消息传递,这意味着:

❝ Flutter 和 Native 之间需要使用 platform channels 来封装对平台线程的调用,而不是能够直接从 Dart 调用 API(即通过 FFI),这让一些简单的同步调用增加了性能损耗和无意义的异步行为。

而从 3.29 开始,Android 和 iOS 上的 Flutter 将在应用的主线程上执行 Dart 代码,并且不再有单独的 Dart UI 线程

❝ 这是改进移动平台上 Native 和 Dart 互操作系列调整中的第一部分,因为它将允许对平台进行同步调用和从平台进行同步调用,并且不会产生序列化和消息传递的开销。

而当双方处于同一个线程下时,同步响应和调用可以更好处理一些平台事件处理、文本输入、插件调用和辅助功能等。

❝ 特别是在对于 PlatformView 混合渲染等场景,如果处于同一线程之上,那么一些场景下的 PlatformView 由于不同线程导致的闪烁或者同步问题或者也可以得到改善。

当然, Eric 也在 150525#issuecomment-2652547816 提到合并线程后的忧虑,比如 Dart 和 Native 平台同一线程之后,那么「滚动/动画」是否会因此出现相互影响,特别是第三方插件处理不当的时候,反而更加卡顿的情况。

当然,在整个 Flutter 团队的目标里,完全剔除 platform/message channels 是必然的方向,未来整个异步 channel 肯定会被彻底“消灭”

DevTools and IDEs 新的 DevTools inspector

默认情况下,3.29 下所有用户都启用了新的 DevTools inspector,新的 inspector 具有一个精简的 Widget Tree 和一个全新的 Widget 属性视图,以及一个自动更新以响应热重载和导航事件的选项。

打开网易新闻 查看更多图片

通过全新的 inspector ,开发者可以更直观地调试布局问题和诊断布局问题:

打开网易新闻 查看更多图片

打开网易新闻 查看更多图片

如果你想关闭它,目前可以从 Inspector settings 对话框中禁用它:

打开网易新闻 查看更多图片

❝ 更多可见:https://docs.flutter.dev/tools/devtools/inspector#new对设备上 Widget 选择的更改

从 DevTools 检查器启用 widget 选择模式后,设备上的任何选择都被视为 widget 选择。

以前在初始 Widget 选择后,开发者需要单击设备上的Select widget按钮,然后选择另一个小组件,而现在有一个设备上的按钮,可用于快速退出 Widget 选择模式:

打开网易新闻 查看更多图片

日志记录工具改进

DevTools 中的 Logging 工具在 3.29 更新中得到了一些改进:

  • 日志包含并显示更多元的数据,例如日志严重性、类别、区域和 isolate

  • 添加了对按日志严重性级别进行筛选的支持

  • 性能和初始加载时间的显著改进

打开网易新闻 查看更多图片
image-20250212121519602重大更改和弃用

本次重大更改和弃用影响范围还是比较大的。

一些 package 将停止支持

以下过去由官方提供的 package ,计划在 2025 年 4 月 30 日后停止支持,关于这些包后续可由第三方协调建立和维护分叉:

  • ios_platform_images planned to be discontinued #162961

  • css_colors planned to be discontinued #162962

  • palette_generator planned to be discontinued #162963

  • flutter_image discontinued #162964

  • flutter_adaptive_scaffold planned to be discontinued #162965

  • flutter_markdown planned to be discontinued #162966

删除 Flutter Gradle 插件

3.29 移除了 Flutter Gradle 插件,这个在很久之前就提到了,该插件其实自 3.19 起已被弃用,后续将把 Flutter Gradle 插件从 Groovy 转换为 Kotlin,并将其迁移到使用 AGP 公共 API,这个改动有望降低发布新 AGP 版本时损坏的频率,并减少基于构建的回归。

❝ 不得不说,现在 Android 平台自己的 AGP 兼容问题越来越麻烦,坑越来越多。

如果是在 3.16 之前创建但尚未迁移的项目可能会受到影响,比如 Flutter 工具在构建项目时会有警告:“You are applying Flutter's main Gradle plugin imperatively”,则基本可以确定会受到这个变动的影响,开发者需要根据 docs.flutter.dev 上提供的方式进行迁移。

❝ 更多可见:https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply
删除 Web HTML renderer

这个在之前的 已经聊到过,而从 3.29 开始, HTML renderer 就被正式移除了。

同时正如前面所说的,一些由于 WebAssembly 带来的缺失也逐步完善,例如 CORS images 和通过语义树来适配的 SEO 等场景都在补齐:

打开网易新闻 查看更多图片
Material changes

作为 Material 中正在进行的主题规范化项目的一部分,3.29 弃用ThemeData.dialogBackgroundColor并迁移到了DialogThemeData.backgroundColor,开发者可以使用dart fix命令迁移受影响的代码。

同样在 Material 中,ButtonStyleButtoniconAlignment属性在添加到ButtonStyle和关联的styleFrom方法后已被弃用。

多窗口

最后,不得不提提及 PC 端多窗口支持的进展,在去年的 官方已经向我们展示了多窗口的可行性,而从 #142845 看目前推进的进度还可以:

打开网易新闻 查看更多图片

而从 #30701 可以看到,不久之后对应的 PR 草稿将正式开始进行审查,所以相信今年应该可以看到期盼已久的多窗口落地:

打开网易新闻 查看更多图片
宏支持停止

关于 Dart 宏支持部份,正如 所说,由于宏的性能具体目标还太遥远,Flutter 团队决定把当前的实现回归到编辑(例如静态分析和代码完成)和增量编译(热重载的第一步)上,并且具体在于重新投资Dart 中的数据支持,从聚焦于而优化数据序列化和反序列化问题。

最后

可以看到 ,Flutter 3.29 带来了不少新功能的同时,也引入了不少大变动,所以如果你想将生产项目升级到 3.29 ,那么在「稳定」和「可控」评估上就需要更加谨慎,至少也要等到3.29.3再行动不迟。

那么,勇士们准备好吃螃蟹了吗

  • https://medium.com/flutter/f90c380c2317