This commit is contained in:
guoshuyu 2023-06-15 16:24:03 +08:00
parent af12c9c50e
commit 2b90dc51c7
4 changed files with 143 additions and 0 deletions

View File

@ -80,6 +80,7 @@
* [Flutter 小技巧之横竖列表的自适应大小布局支持](Flutter-N24.md)
* [Flutter 3.10 之 Flutter Web 路线已定,可用性进一步提升,快来尝鲜 WasmGC](Flutter-IOW.md)
* [Flutter 3.10 适配之单例 Window 弃用,一起来了解 View.of 和 PlatformDispatcher](Flutter-310Win.md)
* [Flutter 小技巧之 3.10 全新的 MediaQuery 优化与 InheritedModel ](Flutter-N25.md)

139
Flutter-N25.md Normal file
View File

@ -0,0 +1,139 @@
# Flutter 小技巧之 3.10 全新的 MediaQuery 优化与 InheritedModel
关于 `MediaQuery` 我们介绍过不少,比如在之前的[《MediaQuery 和 build 优化你不知道的秘密》](https://juejin.cn/post/7114098725600903175)里就介绍过,**要慎重在 `Scaffold` 之外使用 `MediaQuery.of(context)`** ,这是因为 `MediaQuery.of``BuildContext` 的绑定可能会导致一些不必要的性能开销,例如键盘弹起时,会导致相关的 `MediaQuery.of(context)` 绑定的页面出现重构。
比如下面这个例子,我们在 `MyHomePage` 里使用了 `MediaQuery.of(context).size` 并打印输出,然后跳转到 `EditPage` 页面,弹出键盘 ,这时候会发生什么情况?
```dart
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("######### MyHomePage ${MediaQuery.of(context).size}");
return Scaffold(
body: Container(
alignment: Alignment.center,
child: InkWell(
onTap: () {
Navigator.of(context).push(CupertinoPageRoute(builder: (context) {
return EditPage();
}));
},
child: new Text(
"Click",
style: TextStyle(fontSize: 50),
),
),
),
);
}
}
class EditPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text("ControllerDemoPage"),
),
extendBody: true,
body: Column(
children: [
new Spacer(),
new Container(
margin: EdgeInsets.all(10),
child: new Center(
child: new TextField(),
),
),
new Spacer(),
],
),
);
}
}
```
如下图 log 所示 可以看到在键盘弹起来的过程,因为 bottom 发生改变,所以 `MediaQueryData` 发生了改变,从而导致上一级的 `MyHomePage` 虽然不可见,但是在键盘弹起的过程里也被不断 build 。
![](http://img.cdn.guoshuyu.cn/20230602_N25/image1.png)
虽然在之前的[小技巧](https://juejin.cn/post/7114098725600903175)里我们介绍了解决方式,但是 3.10 开始有更优雅的做法,同时也更方便我们自足控制更细的颗粒度地去管理 `InheritedWidget` 里的绑定关系,那就是使用 `InheritedModel`
# MediaQuery
在 3.10 里 `MediaQuery` 增加了需要针对特定参数的 `****of` 方式,例如 `MediaQuery.platformBrightnessOf(context);` ,这些方法对应在 `_MediaQueryAspect` 里都有一个枚举类型,而在 Flutter Framework 里,这些参数的调用都修改成了新的 `****of` 类型方法。
![](http://img.cdn.guoshuyu.cn/20230602_N25/image2.png)
例如一开始的例子,只需要将 `MediaQuery.of(context).size` 修改为 `MediaQuery.sizeOf(context)` ,那么跳转到 `EditPage` 页面,弹出键盘 ,在键盘弹起来的过程中不会再导致 `MyHomePage` rebuild 输出 log。
而之所以会这样的原因,其实是因为这些 `MediaQuery.******Of(context);` 内部调用的是 `InheritedModel.inheritFrom` 实现。
是的3.10 开始 [`MediaQuery` 继承从 `InheritedWidget` 变成 `InheritedModel`](https://github.com/flutter/flutter/commit/73cb7c2fc5ecab0476ef5d41d1227ed09f73db56) ,而 **`InheritedModel``inheritFrom` 方法可以让开发者可以通过 `aspect` 来决定数据改变时是否调用对应更新**。
![](http://img.cdn.guoshuyu.cn/20230602_N25/image3.png)
![](http://img.cdn.guoshuyu.cn/20230602_N25/image4.png)
> 小技巧一:**3.10 现在可以通过将 `MediaQuery.of` 获取参数的方式替换成 `MediaQuery.******Of(context);` 来减少不必要的 rebuild** 。
# InheritedModel
使用 `InheritedModel` 只需要继承它就可以,之后**需要重点关注的是 `updateShouldNotifyDependent` 方法,它用于决定应该什么时候 rebuild** 。
如下图所示是 `MediaQuery` 的实现,在 `updateShouldNotifyDependent` 里我们可以通过 `dependencies` 里的类型来进行区分,比如调用时是通过 `InheritedModel.inheritFrom<MediaQuery>(context, aspect: _MediaQueryAspect.size)` 输入,那么判断时就会进入到 `_MediaQueryAspect. size` 这个 ` case`
![](http://img.cdn.guoshuyu.cn/20230602_N25/image5.png)
如果此时 `size` 参数发生了改变,就返回 `true` ,从而产生 rebuild反之返回放 false这就是 `InheritedModel` 可以根据绑定具体变量来更新页面的原因。
> 当然这里你不一定要传枚举,你喜欢的话传 String 也可以,具体可以根据你的爱好来设定。
那为什么 `InheritedModel``inheritFrom` 方法可以达到这样的效果?
我们简单看一下 `inheritFrom` 的源码实现,如下图所示:
- 在没有 `aspect` 的时候直接调用 `dependOnInheritedWidgetOfExactType()` 那就是和之前普通的 `of(context)` 没什么区别
- 在有 `aspect` 的时候,会先通过 `_findModels` 找到对应的 `InheritedElement` ,然后调用 `dependOnInheritedElement()` 绑定
![](http://img.cdn.guoshuyu.cn/20230602_N25/image6.png)
可能这时候你有些疑问,不要急慢慢来,我们先看 `dependOnInheritedWidgetOfExactType()``dependOnInheritedElement()` 其实 `dependOnInheritedWidgetOfExactType()` 内部也是调用 `dependOnInheritedElement()` 来完成绑定,那么这里前后的区别是什么?
如果你仔细看,**这里的区别在于 `dependOnInheritedElement()` 多使用了 `aspect`** ,对应到 `MediaQuery` 里就是如 `_MediaQueryAspect.size` 这个 `aspect` ,这样在后续 `updateShouldNotifyDependent` 时就会被用上。
如下图所示是 `InheritedModel``InheritedModelElement` ,可以看到 **`inheritFrom` 传入的 `aspect` 会变成 `dependencies`** ,而这个` dependencies` 就是我们在 `updateShouldNotifyDependent` 里用来判断的类型依据。
![](http://img.cdn.guoshuyu.cn/20230602_N25/image7.png)
最后,在 `notifyDependent` 方法里可以看到,只有 `updateShouldNotifyDependent` 返回 `true` 时,才会调用 `didChangeDependencies` 去更新。
所以 `inheritFrom` 的特殊之处在于:**当存在 `aspect` 时,该 `aspect` 会变成 `dependencies` 集合,然后通过 `updateShouldNotifyDependent` 来决定是否触发更新**。
至于 `_findModels` 方法其实你无需纠结,虽然它是传入一个 List但是一般情况下你只会获取到一个。什么时候会可能有多个就是你 override 了 `InheritedModel``isSupportedAspect` 方法,并且会根据 `aspect` 条件有不同判断返回时可能会有多个。
![](http://img.cdn.guoshuyu.cn/20230602_N25/image8.png)
例如都是继承 `InheritedModel` ,但是 `isSupportedAspect` 可以根据条件来决定你这个实例是否支持 `Aspect` 绑定。
```dart
@override
bool isSupportedAspect(Object aspect) {
return aspects == null || aspects!.contains(aspect);
}
```
另外 `_findModels` 里用的是 `getElementForInheritedWidgetOfExactType()`,它和 `dependOnInheritedWidgetOfExactType()` 的区别就是前者会注册依赖关系,而后者不会,所以 `_findModels` 顾名思义只是找出符合条件的 `InheritedModel`
![](http://img.cdn.guoshuyu.cn/20230602_N25/image9.png)
# 最后
最后总结一下,今天的小技巧其实很简单,**就是更新你的 `MediaQuery.of` 到对应参数的 `MediaQuery.*****of` 从而提升应用性能**,并且了解到 `InheritedModel` 的实现逻辑和自定义支持,从而学会优化你现在的 `InheritedWidget ` 的使用。
如果你还有什么问题,欢迎留言评论交流。

View File

@ -185,6 +185,7 @@
* [Flutter 小技巧之横竖列表的自适应大小布局支持](Flutter-N24.md)
* [Flutter 3.10 之 Flutter Web 路线已定,可用性进一步提升,快来尝鲜 WasmGC](Flutter-IOW.md)
* [Flutter 3.10 适配之单例 Window 弃用,一起来了解 View.of 和 PlatformDispatcher](Flutter-310Win.md)
* [Flutter 小技巧之 3.10 全新的 MediaQuery 优化与 InheritedModel ](Flutter-N25.md)
[Flutter 工程化选择](GCH.md)

View File

@ -229,6 +229,8 @@
* [Flutter 3.10 适配之单例 Window 弃用,一起来了解 View.of 和 PlatformDispatcher](Flutter-310Win.md)
* [Flutter 小技巧之 3.10 全新的 MediaQuery 优化与 InheritedModel ](Flutter-N25.md)
* [Flutter 工程化选择](GCH.md)
* [Flutter 工程化框架选择——搞定 Flutter 动画](Z1.md)
* [Flutter 工程化框架选择 — 搞定 UI 生产力](Z3.md)