GSYFlutterBook/Flutter-310Win.md

120 lines
8.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Flutter 3.10 适配之单例 Window 弃用,一起来了解 View.of 和 PlatformDispatcher
Flutter 3.10 发布之后,大家可能注意到,在它的 [release note](https://juejin.cn/post/7231565908631633979#heading-46) 里提了一句: **Window singleton 相关将被弃用,并且这个改动是为了支持未来多窗口的相关实现**
> 所以这是一个为了支持多窗口的相关改进,多窗口更多是在 PC 场景下更常见,但是又需要兼容 Mobile 场景,故而有此次改动作为提前铺垫。
如下图所示,如果具体到对应的 API 场景,主要就是涉及 `WidgetsBinding.instance.window``MediaQueryData.fromWindow` 等接口的适配,因为 `WidgetsBinding.instance.window` 即将被弃用。
![](http://img.cdn.guoshuyu.cn/20230517_310/image1.png)
> 你可以不适配,还能跑,只是升级的技术债务往后累计而已。
那首先可能就有一个疑问,为什么会有需要直接使用 `WidgetsBinding.instance.window` 的使用场景?简单来说,具体可以总结为:
- 没有 `BuildContext` ,不想引入 `BuildContext`
- 不希望获取到的 ` MediaQueryData` 受到所在 `BuildContext` 的影响,例如键盘弹起导致 padding 变化重构和受到 `Scaffold` 下的参数影响等
> 这部分详细可见:[《MediaQuery 和 build 优化你不知道的秘密》 ](https://juejin.cn/post/7114098725600903175)。
那么从 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` 相关的调整为:
```dart
/// 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` 的实现上。
![](http://img.cdn.guoshuyu.cn/20230517_310/image2.png)
简单总结一下:**在存在 `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` 场面**
![](http://img.cdn.guoshuyu.cn/20230517_310/image3.png)
> `PlatformDispatcher` 内部的` views` 维护了中所有可用 `FlutterView` 的列表,用于提供在没有 `BuildContext` 的情况下访问视图的支持。
你说什么情况下会有没有 `BuildContext` ?比如 Flutter 里 的 `runApp` 如下图所示3.10 目前在 `runApp` 时会通过 `platformDispatcher.implicitView` 来塞进去一个默认的 `FlutterView`
![](http://img.cdn.guoshuyu.cn/20230517_310/image4.png)
`implicitView` 又是什么?其实 `implicitView` 就是 `PlatformDispatcher._views` 里 id 为 0 的 `FlutterView` ,默认也是 `views` 这个 `Iterable` 里的 `first` 对象。
![](http://img.cdn.guoshuyu.cn/20230517_310/image5.png)
也就是在没有 `BuildContext` 的场景, 可以通过 `platformDispatcher.views.first` 的实现迁移对应的 `instance.window` 实现。
```dart
/// 3.10 之前
MediaQueryData.fromWindow(WidgetsBinding.instance.window)
/// 3.10 之后
MediaQueryData.fromView(WidgetsBinding.instance.platformDispatcher.views.first)
```
为什么不直接使用 `implicitView` 对象? 因为 `implicitView` 目前是一个过渡性方案,官方希望在多视图的场景下不应该始终存在 implicit view 的概念,而是应用自己应该主动请求创建一个窗口,去提供一个视图进行绘制。
![](http://img.cdn.guoshuyu.cn/20230517_310/image6.png)
所以对于 `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` 去兼容支持的逻辑所在。
| ![](http://img.cdn.guoshuyu.cn/20230517_310/image7.png) | ![](http://img.cdn.guoshuyu.cn/20230517_310/image8.png) | ![](http://img.cdn.guoshuyu.cn/20230517_310/image9.png) | ![](http://img.cdn.guoshuyu.cn/20230517_310/image10.png) |
| ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | -------------------------------------------------------- |
# 最后
最后总结一下,说了那么多,其实不外乎就是将 `WidgetsBinding.instance.window` 替换为 `View.of(context)` ,如果还有一些骚操作场景,可以使用 `WidgetsBinding.instance.platformDispatcher.views` ,如果不怕后续又坑,甚至可以直接使用 `WidgetsBinding.instance.platformDispatcher.implicitView`
整体上解释那么多,**主要还是给大家对这次变动有一个背景认知,同时也对未来多窗口实现进展有进一步的了解**,相信下一个版本多窗口应该就可以和大家见面了。
更多讨论可见:
- https://github.com/flutter/flutter/issues/120306
- https://github.com/flutter/engine/pull/39553
- https://github.com/flutter/flutter/issues/116929
- https://github.com/flutter/flutter/issues/99500
- https://github.com/flutter/engine/pull/39788