观摩!Flutter 1.22 正式发布

11,739 阅读15分钟

继 9 月 23 号发布 Flutter Windows 内测版 之后刚过几天,Flutter 官方在昨夜凌晨正式发布了 Flutter 1.22。


本次版本的升级又带来了新一轮的功能发布,性能改进和问题修复。恰逢移动平台新版本(iOS 14/Android 11)的发布季,此次的版本更新保证了 Flutter 应用在 Android 11 和 iOS 14 上的兼容性,面向 iOS 14,本次更新包括了对 Xcode 12,新 Icon 的更新以及 App Clips 功能的预览。对于Android 11,此次更新包括了多种屏幕适配以及软键盘动画的流畅性优化。


距离上个版本发布刚刚两个月,此次版本的更新最为快速,但质量却依然没有下降,Github 数据显示此次更新共解决了 3,024 个 issue,合并了 197 个贡献者的 1,944 个PR,而在这些贡献者中有 114 位(58%)来自社区的支持,他们共提交了 271 个PR,贡献量最大的是 a14n,共提交了 20 个 PR。


除了对新平台的全力支持外,Flutter 的本次更新也迎来了很多值得分享的话题,包括社区讨论最为热烈的 Android 状态恢复,新的 Material 按钮组件以及国际化和本地化支持与热重载并用等功能。此次更新也包括了全新的导航器(Navigator),稳定版 Platform Views (支持 Google Maps 和 WebView 插件)以及高频率设备下滚动性能的优化,同时,开发工具的更新也迎来了另一番景象,具体读者们详见正文。

新平台适配

iOS 14

每次 Android/iOS 等平台推出系统的新版本时,Flutter 都会进行全面的整改来避免出现不兼容的现象。因此,iOS 14 的发布也推动了 Flutter 的新一轮更新,主要包括如下几点:

  • XCode 12 仅支持 iOS 9.0 以上的版本,因此 Flutter 项目已将默认支持的版本从 8.0 更新到了 9.0。

  • Flutter 1.22 修复了在 iOS 14 系统下的的闪退和字体渲染的问题。

  • 自 Flutter 1.20.4 起也已完全解决了部署到真机设备上出现的各种问题。

  • 当应用程序访问其剪贴板时,会导致 Flutter 应用程序弹出错误通知,该问题也已在 1.20.4 中解决。

  • iOS 14 上存在禁止在设备上运行 debug 应用程序的限制(实际开发调试过程除外)。

  • 考虑到网络安全性的问题,本地调试的 Flutter 应用程序会在 iOS 14 上显示一次性确认对话框(仅开发期间)。

如果你的 Flutter 应用程序需要运行在 iOS 14 系统上,我们强烈建议你将 Flutter 版本更新到 1.22 并立即部署到 App Store 中,这样可以确保你的 iOS 14 用户获得最佳体验。

有关 Flutter 如何适配 iOS 14 的更多信息,包括如何添加到原生应用、deep linking 等问题,可以参阅官网 iOS 14文档

我们的目标一直是希望开发者们能完全脱离所有工具和 SDK 的更新而者专注于应用本身的业务逻辑,这就要求我们需要充分支持 iOS 14 的各种新的特性。

本次,我们就针对 iOS 新发布的 SF Symbols 字体做了更新支持,对 cupertino_icon 库做了一系列的更新,现在只需要将 cupertino_icon 更新到最新的 1.0 版本,就能自动将 CupertinoIcons 映射成新样式的图标,Flutter 1.22 后,CupertinoIcons 也额外提供了 900 个新图标。

读者们可以在 cupertino_icons 预览页面上看到完整的图标列表,详情参见迁移详细信息页面

开发者可以在 iOS 14 上 尝试使用 Flutter 的另一个功能就是 App Clips(轻应用),这是 iOS 14 推出的一项新功能,它支持 10MB 以下轻量级应用程序的快速,免安装打开,而在 1.22 版后,我们就可以试一下 Flutter 在 iOS 上支持的 App Clip 功能了。

Flutter App Clip
Flutter App Clip

有关如何使用 Flutter 构建 App Clip 的更多详细信息,参见官方文档,也可以参考这个简单的示例项目

Android 11

Flutter 的此次更新也同样同步了本月发布的 Android 11。为了支持 Android 11 中引入的两个新功能,Flutter 框架层和引擎层都已做了相应的更新。首先,Flutter 现在已经支持多种全新 Android 屏幕的适配,如下图:

通过使用 MediaQuerySafeArea 这两个组件,开发者就可以确保将展示的 UI 和交互式组件放置在设备显示屏的无障碍区域中。另外,目前我们需要尽量避免在瀑布屏边缘区域使用手势检测器,因为这些手势检测器可能会导致意外触摸。

其次,显示软件键盘时的动画也已经与 Android 11 同步。

此前, Flutter 一直存在 #19279 这个问题,其中系统键盘的显示/隐藏动画与 Flutter 并不同步,这个问题也已经在此次更新中被修复。

关于 Android 嵌入 API 的注释。去年,Flutter 1.12 推出了一套全新的 Flutter 插件 API,我们开发了 v2 API 使开发者们能够更好的将 Flutter 嵌入到已有的原生应用中。据我们统计,到目前为止已经有超过 80% 的 Android 插件使用了新的 Android API 了,因此,从本次发布 1.22 开后,我们便不再维护旧的 v1 API。

如果你仍在使用 Android v1 API,可能会导致如下问题:

  • 无法使用新开发的插件
  • Flutter工具的 —no-enable-android-embedding-v2config 标志已被默认删除
  • 仍在使用 v1 API 的旧版应用程序在构建期间会显示弃用警告,并指向支持新的Android插件API文档

同时,如果你仍然有基于 v1 Android API 的 Flutter 应用程序,它虽然能够正常运行,但是很可能会使用遇到仅支持 v2 API 的新插件,而这些插件不能被 v1 Android API 使用。更多详细信息,请参见重大更改文档

全新的 Button 组件

之前的版本中,Flutter 已经有了一套完备的按钮组件,但使用起来却很麻烦,Material 规范也增加了多个新样式的按钮。所以,为了使 Flutter 保持与 Material 的同步,我们正式地宣布 Flutter 1.22 将引入全新的 “Button” 按钮。

新的 Button 组件的命名规范也与 Material Design 设计原则,如下。

DartPad 上有一个很好的示例。 另外,旧的组件如FlatButtonOutlineButtonRaisedButtonButtonBarButtonBarTheme也并不会被弃用,开发可以按照需求混合使用旧按钮与新按钮。

全新的国际化和本地化的支持

自 Flutter 发布以来,已经为应用提供了较好的国际化(i18n)和本地化(l10n)所需的核心功能的支持,而在此次的新版本中,我们也将该功能的最佳实践纳入了我们的开发工具中,并且,在添加新的 l10n 信息时启用了热重装支持来直接更新应用程序。

如果你想了解有关 Flutter l10n 的更多信息,包括本地化消息,带有参数,日期,数字和货币的消息,请参见Flutter Internationalization 用户指南

此外,如果你对 i18n 和 l10n 感兴趣,你可能还对那些字符串不包含在普通 ASCII 字符的字符串,例如 Unicode 和 emoji 的问题比较惯性。本次,Dart 团队也发布了 characters 软件包可以帮助开发人员处理 Unicode(扩展)字符簇。该库可以帮助开发者们解决诸如如何正确地将字符串(如“ A 🇬🇧 text in English”)缩写为前 15 个字符的问题,使用 String 类,该字符串可以缩写为 “ A 🇬🇧 text in”,它仅是 12 个用户可感知的字符。另一方面,使用 characters 也可以生成 “A 🇬🇧 text in Eng” 的正确缩写。

此PR 使用 characters 完美的处理了这些复杂的字符,例如,当 TextField 带有最大长度 maxLength 限制时,像 👨‍👩‍👦 这样的字符现在可以正确地算作单个字符,另外,此PR,在Flutter所在的项目中,字符包均可自动在项目中使用,而无需手动添加。希望这使得处理来自所有语言环境的各种字符串变得更加容易。有关 character 包的更多详细信息,请查看文章正确完成Dart字符串操作

Google Maps 和 WebView 插件准备投入生产

Flutter 团队通常会经过仔细考虑后才会将某些标签标记为 “production ready”,在此之前,我们通常都会对其进行了全面测试。对于google_maps_flutterwebview_flutter 这两个插件底层都是使用 Platform Views 实现,从而允许将 Android 和 iOS 的原生 UI 组件嵌入在 Flutter 应用程序中。在此次的 Flutter 版本中,我们欣然宣布,我们已经对框架层进行了强化,完全能够将这两个插件都声明为可投入生产(即 “production ready”)。

在 Flutter 1.22 中,我们添加了一个替代的 Platform Views 实现,该实现修复了所有已知的键盘以及 Android 视图的可访问性问题。此外,它还适用于 19 级及以上的 Android API(以前要求 20 级)。我们还对 iOS 上的线程进行了改进,使平台视图更高效,更可靠(并且不再需要你将 io.flutter.embedded_views_preview 标志添加到 iOS 中 Info.plist

webview_flutter 插件支持新的 Android Platform Views 模式,但当前需要手动启用。一旦在更广泛的社区中得到更多使用,我们将默认在将来的版本中启用它。

Google Maps 和 WebView 插件已经从 Platform Views 的改进中受益。如果你想使用平台视图在 iOS 或 Android 上嵌入自己的原生 UI 组件,可以参阅如何 Hosting native Android and iOS views in your Flutter app with Platform Views

Navigator 2.0

如果你之前在 Flutter 应用程序中使用过 Navigator,则可能已经注意到核心数据结构(用户正在浏览的页面路由堆)对你是不可见的。每次要进行管理时,需要调用 Navigator.pop()Navigator.push()。例如,假设你要在主页上显示一系列组件,并允许用户点击一个组件以进入该颜色的详细信息页面,如下图。

我们可以使用下面这种方式实现这两个简单的 UI 页面:

class ColorListScreen extends StatelessWidget {
 final List<Color> colors;
 final void Function(Color color) onTapped;
 ColorListScreen({this.colors, this.onTapped});
 
 @override
 Widget build(BuildContext context) => Scaffold(
       appBar: AppBar(title: Text('Colors')),
       body: Column(
         children: [
           // you can see and decide on every color in this list
           for (final color in colors)
             Expanded(
               child: GestureDetector(
                 child: Container(color: color),
                 onTap: () => onTapped(color),
               ),
             )
         ],
       ),
     );
}
 
class ColorScreen extends StatelessWidget {
 final Color color;
 const ColorScreen({this.color});
 
 @override
 Widget build(BuildContext context) => Scaffold(
       appBar: AppBar(title: Text('Color')),
       body: Container(color: color),
     );
}

使用下面这种 Navigator 1. 0 方式,可以非常简单地实现在这两个页面之间的导航:

class _ColorAppState extends State<ColorApp> {
 List<Color> _colors = [Colors.red, Colors.green, Colors.blue];
 
 @override
 Widget build(BuildContext context) => MaterialApp(
       title: 'Color App',
       home: Builder(
         builder: (context) => ColorListScreen(
           colors: _colors,
           // the Navigator manages the list of pages itself; you can only push and pop
           onTapped: (color) => Navigator.push(
             context,
             MaterialPageRoute(builder: (context) => ColorScreen(color: color)),
           ),
         ),
       ),
     );
}

只需调用 Navigator.push() ,即可在第一个页面打开第二个页面,从而在路由栈中创建两个页面的实例,但是,和在 ColorListScreen 中的 build 方法中显式地创建 Containers 列表不同,该路由栈并不可见,因此很难管理一些特殊情况,如处理由原生嵌入提供的初始路由的 deep linking,或者来自 Web 的 URL 或来自 Android 的 intent,管理同一页面的不同顺序之间的嵌套路由也极其困难。

Navigator 2.0 通过使页面堆栈可见解决了这些问题,甚至更多。下面这段代码是在 ColorListScreenColorScreen 之间的另一个版本 :

class _ColorAppState extends State<ColorApp> {
 Color _selectedColor;
 List<Color> _colors = [Colors.red, Colors.green, Colors.blue];
 
 @override
 Widget build(BuildContext context) => MaterialApp(
       title: 'Color App',
       home: Navigator(
         // you can see and decide on every page in this list
         pages: [
           MaterialPage(
             child: ColorListScreen(
               colors: _colors,
               onTapped: (color) => setState(() => _selectedColor = color),
             ),
           ),
           if (_selectedColor != null) MaterialPage(child: ColorScreen(color: _selectedColor)),
         ],
         onPopPage: (route, result) {
           if (!route.didPop(result)) return false;
           setState(() => _selectedColor = null);
           return true;
         },
       ),
     );
}

这里显式地创建了一个 Navigator,并为其提供代表完整堆栈的页面列表,我们创建一个空 _selectedColor 变量来表示尚未选择任何颜色,因此默认不显示 ColorScreen。当用户选择一种颜色时,我们调用 setState() 更新状态,Flutter 会重新调用 build() 方法,然后就会在 ColorScreen 顶部创建一个 ColorScreen 页面。

你可以在 OnPopPage 回调函数中更新返回的状态,例如,如果用户回退,则表示他们 “取消选择” 了当前颜色,从而 _selectedColor = null 表示不再希望显示该页面。

Navigator 2.0 看起来像 Flutter 的其余部分,那正是她的意图,它是声明性的,与 Navigator 1.0 势在必行,这个想法是要在导航和Flutter 的其余部分之间统一模型,同时解决许多问题并添加功能。实际上,这个小例子几乎还不涉及 Navigator 2.0 的内容。有关详细信息,推荐阅读 Declarative navigation and routing in Flutter

另外,Navigator 1.0 依然可以继续使用,短期内也不会失效,如果你已经喜欢这种路由模式,完全可以继续使用它。但是,如果你尝试使用 Navigator 2.0,我们认为你会喜欢的。

预览功能

Android 的状态还原

在此次的新版本中还能够试用的新功能是 对Android的状态还原的 支持。这是我们在 Github 上最受欢迎的功能之一,拥有 217 个点赞!

考虑到读者们可能不熟悉状态还原这个需求。移动操作系统可能会杀死后台的应用程序,以回收前台应用程序的资源。发生这种情况时,操作系统会通知该应用已经被终止,这样开发者就可以快速保存当前 UI 状态,以便在用户再次回到该应用时可以将其恢复。如果该功能完善,就可以为用户提供无缝的体验了,同时也可以更好地利用设备的资源。目前,Flutter 还并不支持状态还原,如果没有框架层的支持,也很难自行地进行状态地还原,因此,在 Flutter 1.22 中我们也宣布推出该功能的基础实现。

下面是一个用于恢复默认 Flutter Counter 应用状态的简单示例:

class CounterState extends State<RestorableCounter> with RestorationMixin {
  @override
  String get restorationId => widget.restorationId;

  RestorableInt _counter = RestorableInt(0);

  @override
  void restoreState(RestorationBucket oldBucket) => registerForRestoration(_counter, 'count');

  void _incrementCounter() => setState(() => _counter.value++);

  @override
  Widget build(BuildContext context) => Scaffold(
      body: Center(child: Text('${_counter.value}')),
      floatingActionButton: FloatingActionButton(onPressed: _incrementCounter),
    );
}

简要地说,每个组件都有一个存储桶(storage bucket),RestorationMixin 使用唯一的 ID 向其注册。通过使用一种RestorableProperty 类型(如这里的 RestorableInt)来存储特定于 UI 数据,并使用状态恢复功能注册该数据,该数据将在 Android 终止该应用程序之前自动保存,并在其再正常运行时进行恢复。就是这样,Restoration* 可以保存任何类型的数据,如RestorableIntRestorableStringRestorableTextEditingController(等等)都将可以被恢复。

如果系统内置没有涵盖你要还原的数据类型,也可以通过 RestorableProperty<T> 创建自己的类型 。

为了实现状态恢复的自动测试,我们也向 WidgetTester 增加了全新的 restartAndRestore API。如果想要手动测试,最简单的方法就是在 Android 设备上打开已经启用状态恢复的 Flutter 应用,在 Android 开发人员设置中启用“不要保留活动”(如下图),然后运行 Flutter 应用,将其置于后台,之后再返回。此时,Android 系统就会先终止再恢复你的应用程序了,你可以查看一切是否按预期工作。

虽然我们已经推出了状态恢复的预览版,但还有很多其他的工作要做。例如,状态恢复不仅需要适用于 Android,iOS 应用程序也应当及时同步。此外,我们也正着手优化自己的内置组件,以在恢复过程中默认保持其状态。我们已经在 ListViewSingleChildScrollView(记住用户的滚动位置)和 TextFields (恢复他们输入的文本)类中提供了该功能支持,我们也正计划将其扩展到其他组件中。

然而,由于 navigation(1.0 或 2.0)的原因,该功能目前也才出预览版,,也就是说,你的用户还不能体验该功能,我们即将会在 Beta 中发布,并在 Flutter 的下一个稳定版本中正式发布。

滚动性能优化

由于存在输入和显示频率不同步的情况,Flutter 团队也与 Google 内核部门合作,极大地提高了页面滚动性能。例如,Pixel 4 输入的运行频率为 120hz,而显示屏的运行频率为 90hz,滚动时,这种不匹配会导致性能下降。使用新的 resamplingEnabled 标志,你就可以解决此问题,如下:

void main() {
  GestureBinding.instance.resamplingEnabled = true;
  run(MyApp());
}

根据所涉及的频率差异,启用此标志可以使滚动时的颤动减少到 97%,当我们确定这已经是最好的体验时,我们已经计划在以后的版本中默认启用此功能。

延伸阅读

由于掘金篇幅限制,本篇文章并没有涉及 Flutter 开发者工具的更新,你可以阅读:

官方原文:https://medium.com/flutter/announcing-flutter-1-22-44f146009e5f

我的博客:https://meandni.com/2020/09/28/d206/

关注公众号「Meandni」,及时阅读移动开发技术和最新技术动态。