Flutter 小技巧之 310 适配之单例 Window

Flutter 3.10 发布之后,大家可能注意到,在它的 release note 里提了一句: Window singleton 相关将被弃用,并且这个改动是为了支持未来多窗口的相关实现。

所以这是一个为了支持多窗口的相关改进,多窗口更多是在 PC 场景下更常见,但是又需要兼容 Mobile 场景,故而有此次改动作为提前铺垫。

如下图所示,如果具体到对应的 API 场景,主要就是涉及 WidgetsBinding.instance.window 和 MediaQueryData.fromWindow 等接口的适配,因为 WidgetsBinding.instance.window 即将被弃用。

你可以不适配,还能跑,只是升级的技术债务往后累计而已。

那首先可能就有一个疑问,为什么会有需要直接使用 WidgetsBinding.instance.window 的使用场景?简单来说,具体可以总结为:

  • 没有 BuildContext ,不想引入 BuildContext
  • 不希望获取到的 MediaQueryData 受到所在 BuildContext 的影响,例如键盘弹起导致 padding 变化重构和受到 Scaffold 下的参数影响等

这部分详细可见:《MediaQuery 和 build 优化你不知道的秘密》 。

那么从 3.10 开始,针对 WidgetsBinding.instance.window 可以通过新的 API 方式进行兼容:

  • 如果存在 BuildContex , 可以通过 View.of 获取 FlutterView,这是官方最推荐的替代方式
  • 如果没有 BuildContex 可以通过 PlatformDispatcher 的 views 对象去获取

这里注意到没有,现在用的是 View.of ,获取的是 FlutterView ,对象都称呼为 View 而不是 「Window」,对应的 MediaQueryData.fromWindow API 也被弃用,修改为 MediaQueryData.fromView ,这个修改的依据在于:

起初 Flutter 假定了它只支持一个 Window 的场景,所以会有 SingletonFlutterWindow 这样的 instance window 对象存在,同时 window 属性又提供了许多和窗口本身无关的功能,在多窗口逻辑下会显得很另类。

那么接下来就让我们用「长篇大论」来简单介绍下这两个场景的特别之处。

存在 BuildContext

回归到本次的调整,首先是存在 BuildContext 的场景,如下代码所示,对于存在 BuildContex 的场景, View.of 相关的调整为:

1
2
3
4
5
6
7
8
9
10
11
12
ini复制代码/// 3.10 之前
double dpr = WidgetsBinding.instance.window.devicePixelRatio;
Locale locale = WidgetsBinding.instance.window.locale;
double width =
   MediaQueryData.fromWindow(WidgetsBinding.instance.window).size.width;


/// 3.10 之后
double dpr = View.of(context).devicePixelRatio;
Locale locale = View.of(context).platformDispatcher.locale;
double width =
   MediaQueryData.fromView(View.of(context)).size.width;

可以看到,这里的 View 内部实现肯定是有一个 InheritedWidget ,它将 FlutterView 通过 BuildContext 往下共享,从而提供类似 「window」 的参数能力,而通过 View.of 获取的参数:

  • 当 FlutterView 本身的属性值发生变化时,是不会通知绑定的 context 更新,这个行为类似于之前的 WidgetsBinding.instance.window
  • 只有当 FlutterView 本身发生变化时,比如 context 绘制到不同的 FlutterView 时,才会触发对应绑定的 context 更新

可以看到 View.of 这个行为考虑的是「多 FlutterView」 下的更新场景,如果是需要绑定到具体对应参数的变动更新,如 size 等,还是要通过以前的 MediaQuery.of / MediaQuery.maybeOf 来实现。

而对于 View 来说,每个 FlutterView 都必须是独立且唯一的,在一个 Widget Tree 里,一个 FlutterView 只能和一个 View 相关联,这个主要体现在 FlutterView 标识 GlobalObjectKey 的实现上。

简单总结一下:在存在 BuildContex 的场景,可以简单将 WidgetsBinding.instance.window 替换为 View.of(context) ,不用担心绑定了 context 导致重构,因为 View.of 只对 FlutterView 切换的场景生效。

不存在 BuildContext

对于不存在或者不方便使用 BuildContext 的场景,官方提供了 PlatformDispatcher.views API 来进行支持,不过因为 get views 对应的是 Map 的 values ,它是一个 Iterable 对象,那么对于 3.10 我们需要如何使用 PlatformDispatcher.views 来适配没有 BuildContext 的 WidgetsBinding.instance.window 场面?

PlatformDispatcher 内部的views 维护了中所有可用 FlutterView 的列表,用于提供在没有 BuildContext 的情况下访问视图的支持。

你说什么情况下会有没有 BuildContext ?比如 Flutter 里 的 runApp ,如下图所示,3.10 目前在 runApp 时会通过 platformDispatcher.implicitView 来塞进去一个默认的 FlutterView 。

implicitView 又是什么?其实 implicitView 就是 PlatformDispatcher._views 里 id 为 0 的 FlutterView ,默认也是 views 这个 Iterable 里的 first 对象。

也就是在没有 BuildContext 的场景, 可以通过 platformDispatcher.views.first 的实现迁移对应的 instance.window 实现。

1
2
3
4
scss复制代码/// 3.10 之前
MediaQueryData.fromWindow(WidgetsBinding.instance.window)
/// 3.10 之后
MediaQueryData.fromView(WidgetsBinding.instance.platformDispatcher.views.first)

为什么不直接使用 implicitView 对象? 因为 implicitView 目前是一个过渡性方案,官方希望在多视图的场景下不应该始终存在 implicit view 的概念,而是应用自己应该主动请求创建一个窗口,去提供一个视图进行绘制。

所以对于 implicitView 目前官方提供了 _implicitViewEnabled 函数,后续可以通过可配置位来控制引擎是否支持 implicitView ,也就是 implicitView 在后续更新随时可能为 null ,这也是我们不应该在外部去使用它的理由,同时它是在 runApp 时配置的,所以它在应用启动运行后永远不会改变,如果它在启动时为空,则它永远都会是 null。

PlatformDispatcher.instance.views[0] 在之前的单视图场景中,无论是否有窗口存在,类似的 implicitView 会始终存在;而在多窗口场景下,PlatformDispatcher.instance.views 将会跟随窗口变化。

另外我们是通过 WidgetsBinding.instance.platformDispatcher.views 去访问 views ,而不是直接通过 PlatformDispatcher.instance.views ,因为通常官方更建议在 Binding 的依赖关系下去访问 PlatformDispatcher 。

除了需要在 runApp() 或者 ensureInitialized() 之前访问 PlatformDispatcher 的场景。

另外,如下图所示,通过 Engine 里对于 window 部分代码的实现,可以看到我们所需的默认FlutterView 是 id 为 0 的相关依据,所以这也是我们通过 WidgetsBinding.instance.platformDispatcher.views 去兼容支持的逻辑所在。

最后

最后总结一下,说了那么多,其实不外乎就是将 WidgetsBinding.instance.window 替换为 View.of(context) ,如果还有一些骚操作场景,可以使用 WidgetsBinding.instance.platformDispatcher.views ,如果不怕后续又坑,甚至可以直接使用 WidgetsBinding.instance.platformDispatcher.implicitView 。

整体上解释那么多,主要还是给大家对这次变动有一个背景认知,同时也对未来多窗口实现进展有进一步的了解,相信下一个版本多窗口应该就可以和大家见面了。

更多讨论可见:

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%