前言

我们可以通过MediaQuery.of(context)方法获取到一些设备和系统的相关信息,比如状态栏的高度、当前是否是黑暗模式等等,使用起来相当方便,但是也要注意可能引起的页面rebuild问题。本文会介我们可以通过MediaQuery.of(context)方法获取到一些设备和系统的相关信息,比如状态栏的高度、当前是否是黑暗模式等等,使用起来相当方便,但是也要注意可能引起的页面rebuild问题。本文会介绍一个典型的例子,并深入源码来探讨引起rebuild的原因,最后介绍避免rebuild的几个办法。

绍一个典型的例子,并深入源码来探讨引起rebuild的原因,最后介绍避免rebuild的几个办法。

典型例子

以快递App中的查快递场景举例,首页用MediaQuery.of(context).padding.top获取了状态栏高度,用户点击“查快递”按钮会跳转到查快递界面,在查快递界面,用户输入单号可进行查询操作。



当首页的build方法被调用时,会输出我们提前加好的日志。我们发现,当查快递界面的键盘弹出时,首页的build方法被调用了多次:

主界面的build代码如下:

源码探究

既然是因为主界面在build方法里使用了MediaQuery.of(context),从而导致当键盘弹出/隐藏时进行rebuild操作,那么就先来看下MediaQuery类。

MediaQuery

其继承自InheritedWidget,自身并没有重写createElement方法,从flutter三棵树的角度讲,对应的Element即为InheritedElement。有两个属性,data和child,我们可以从data中获取一些设备/系统相关的属性。

另外还有两个比较重要的方法:

fromWindow(key : Key, child : Widget)

此方法直接返回_MediaQueryFromWindow对象,后面会详细介绍。

of(context : BuildContext)

方法里调用了dependOnInheritedWidgetOfExactType,接下来我们详细分析下背后的调用流程。

MediaQuery.of(context) 调用流程

入参是context,本例中的主界面是StatelessWidget,那么这里的context便是StatelessElement。整体调用流程如下:

dependOnInheritedWidgetOfExactType

_inheritedWidgets列表中查询是否有MediaQuery类型的InheritedElement,从三棵树的角度讲,就是从当前节点一直向上查找,找到最近的MediaQuery控件。如果找到,则调用dependOnInheritedElement方法(一般情况下是一定能找到的,下面再详细介绍)。

dependOnInheritedElement

此方法负责将找到的InheritedElement(也就是MediaQuery对应的Element)存起来,并且调用InheritedElement#updateDependencies方法。

updateDependencies

setDependencies

最后两个方法很简单,其作用是将主页对应的StatelessElement存储到了MediaQuery对应的InheritedElement#_dependents中。

研究完MediaQuery.of(context)背后的原理,我们可以知道:通过调用of方法,主界面对应的ElementMediaQuery建立了绑定关系,MediaQuery对应的InheritedElement存储了主界面Element的引用。

Rebuild起点

当介绍dependOnInheritedWidgetOfExactType方法时,我们提道:从当前节点往父节点寻找,一般情况下是一定能找到的MediaQuery控件的。这是因为在WidgetsApp里会自动给我们创建一个根MediaQuery

main方法里,无论使用CupertinoApp还是MaterialApp,最后都会在内部创建WidgetsApp。我们直接看_WidgetsAppState#build方法里的一个代码片段:

会首先检查widget.useInheritedMediaQuery,这个属性默认为false。如果你创建MaterialApp/CupertinoApp时,没有设置useInheritedMediaQuery属性,或者设置了这个属性为null,但找不到MediaQueryData,那么这里就会调用MediaQuery.fromWindow方法。

上面介绍MediaQuery#fromWindow时,我们知道它会创建_MediaQueryFromWindow控件。

_MediaQueryFromWindow的代码不是很多,把和本文相关的代码全部贴出来了,大家可以自己看下,代码如上图所示。

build方法里创建了MediaQuery控件,并实现了didChangeMetrics方法,当手机发生旋转、键盘弹出/隐藏时就会调用此方法,didChangeMetrics内部又调用了setSate,从而导致build方法被重新调用。

通过flutter三颗树的原理我们可以知道,上述所说的“build方法被重新调用”涉及到MediaQueryFromWindow对应的ElementupdateChild方法,简单看下updateChild的内部处理规则:

对MediaQueryFromWindow而言,每次都会创建新的MediaQuery Widget,根据Element#updateChild源码(不是本文讨论重点,不再详细分析其源码)得知,最终会调用MediaQuery对应的Element的update方法。

经过一系列的跳转过后,最终会调用到下面的两个核心方法:

上面介绍的MediaQuery.of(context)方法最终会把入参Context放到_dependents变量里,而这里会遍历这个map,调用每一个ContextdidChangeDependecies方法,didChangeDependecies会将此Context置为dirty状态,下一帧来临时会被重新绘制,并调用此Contextbuild方法。

所以,破案了,当键盘弹起/隐藏时快递主页会被rebuild的原因找到了!

整体的rebuild调用流程如下,感兴趣的可以结合这个调用流程图去看源码:

避免rebuild的办法

研究过源码后,解决方案就变的很简单。

  • 自定义useInheritedMediaQuery属性为true,并在最外面包一层MediaQuery,让WidgetsApp创建时使用MediaQuery,而不去使用监听了application尺寸变化的_MediaQueryFromWindow控件。

  • 避免在页面中使用MediaQuery.of(context)方法,可以使用对应的替代方法,比如本例可以采用下面的代码进行替代,注意单位的转换。

  • 如果必须要使用MediaQuery.of(context)方法,可以使用Builder控件包裹下,of方法的入参传入此Buildercontext即可,这样被rebuild仅是Builder控件包裹下的widget子树。

总结

app界面逐渐复杂时,我们不得不考虑去优化界面性能。本文中介绍的例子在开发中是很常见的,如果不了解MediaQuery.of的机制,可能会引起大量使用此方法的界面发生重绘操作,造成页面卡顿、帧率下降。我们详细分析了背后的源码逻辑,介绍了解决办法,希望能给大家的调优工作提供些许帮助。

作者:京东物流 沈明亮

来源:京东云开发者社区

Flutter调优--深入探究MediaQuery引起界面Rebuild的原因及解决办法的更多相关文章

  1. qt designer启动后不显示界面问题的原因与解决办法

    Qt 5.6.1无论是在vs里双击ui文件还是直接启动designer.exe都一直无法显示界面,但任务管理器中可以看到该进程是存在的.前几天还正常的,但昨天加了一块NVIDIA的显卡(机器自带核显) ...

  2. Qt拖拽界面 (*.ui) 缩放问题及解决办法

    问题 使用Qt Designer 设计的界面,在缩放的时候不能随着主窗口一起缩放. 解决办法 之前遇到这个问题的时候,都是直接重写resizeEvent接口来实现的,在自动生成的Ui_Widget或U ...

  3. Qt拖拽界面 (*.ui) 缩放问题及解决办法(在最顶层放一个Layout)

    问题 使用Qt Designer 设计的界面,在缩放的时候不能随着主窗口一起缩放. 解决办法 之前遇到这个问题的时候,都是直接重写resizeEvent接口来实现的,在自动生成的Ui_Widget或U ...

  4. ctrl+alt+F1~6进入不了字符界面,黑屏的解决办法

    ubuntu系统,我是ubuntu14.04 本来想装cuda,需要在字符界面下装,奈何按ctrl+alt+F1就黑屏了,按ctrl+alt+F7又可以正常回到图形界面,网上查了很多,有的方法也试过, ...

  5. 分享一个android debug模式,出现 waiting for debugger把界面卡住,取巧的解决办法

    使用android studio开发程序时,有时会出现 waiting for debugger 卡住界面,软件无法正常debug运行的情况,很多网友分享了一些解决办法,比如: 1 打开cmd进入命令 ...

  6. cloudera manager的7180 web界面访问不了的解决办法(图文详解)

    说在前面的话 我的机器是总共4台,分别为ubuntucmbigdata1.ubuntucmbigdata2.ubuntucmbigdata3和ubuntucmbigdata4.(注意啦,以下是针对Ub ...

  7. 安装Windows 10后PDF补丁丁等程序界面变得模糊的解决办法

    对于使用高分辨率屏幕且屏幕缩放比例在 100%以上的用户,升级到 Windows 10 后将发现许多程序的界面,例如QQ.电脑管家.Windows本身的服务管理程序等等,都变得非常模糊,<PDF ...

  8. 微信SDK登录无法调起,微信SDK无法接收回调的几种解决办法

    今天有位同事请求帮忙调试微信登录问题,他遇到了以下2个问题,所以,写篇日志备忘,如果有其它朋友遇到此类问题,都可以照此解决! 平时在开发中,有些开发者经常会遇到微信登录SDK登录时,无法调起微信客户端 ...

  9. activemq无法启动且后台管理界面进不去的解决办法

    从官网下载了一个最新的activemq,目前最新版本是5.14.5 我下载的是windows版本,通过执行%activemq home%/bin/win64/InstallService.bat,可以 ...

  10. 【Spark深入学习 -14】Spark应用经验与程序调优

    ----本节内容------- 1.遗留问题解答 2.Spark调优初体验 2.1 利用WebUI分析程序瓶颈 2.2 设置合适的资源 2.3 调整任务的并发度 2.4 修改存储格式 3.Spark调 ...

随机推荐

  1. Python通过ssh登录实现报文监听

    Python自动化ssh登录目标主机,实现报文长度length 0监听,并根据反馈信息弹窗报警: 代码比较简陋,后续记得优化改进. #_*_coding:utf-8 _*_ #!/usr/bin/py ...

  2. Why WebRTC丨“浅入深出”的工作原理详解

    前言 近几年实时音视频通信应用呈现出了大爆发的趋势.在这些实时通信技术的背后,有一项不得不提的技术--WebRTC. 今年 1 月,WebRTC 被 W3C 和 IETF 发布为正式标准.据调研机构 ...

  3. 自己动手从零写桌面操作系统GrapeOS系列教程——18.外设和IO

    学习操作系统原理最好的方法是自己写一个简单的操作系统. 一.外设和I/O接口 前面我们介绍过冯·诺依曼结构包含5部分,其中输入设备和输出设备统称为外部设备,简称外设.常见的外设有鼠标.键盘.显示器.硬 ...

  4. java面向对象-类与对象,构造器

    java面向对象-类与对象,构造器 类与对象 package charpter5.Demo; public class Student { //属性:字段 static String name2=&q ...

  5. LeetCode刷题笔记 - 2022

    这篇博客集中整理在LeetCode的刷题记录,方便查阅 258. 各位相加 - 力扣(LeetCode) (leetcode-cn.com) 代码 class Solution { public: i ...

  6. Vue2依赖收集原理

    观察者模式定义了对象间一对多的依赖关系.即被观察者状态发生变动时,所有依赖于它的观察者都会得到通知并自动更新.解决了主体对象和观察者之间功能的耦合. Vue中基于 Observer.Dep.Watch ...

  7. 中英文拼写检测纠正开源项目使用入门 word-checker 1.1.0

    项目简介 word-checker 本项目用于单词拼写检查.支持英文单词拼写检测,和中文拼写检测. 特性说明 可以迅速判断当前单词是否拼写错误 可以返回最佳匹配结果 可以返回纠正匹配列表,支持指定返回 ...

  8. Java设计模式 —— 外观模式

    13 外观模式 13.1 外观模式概述 Facade Pattern: 为子系统的接口提供一组统一的入口.外观模式定义了一个高层接口,这个接口使得子系统的更加容易使用. 在外观模式中,一个子系统的外部 ...

  9. ORA-17629: Cannot connect to the remote database server

    rman远程连接目标库,提示报错ORA-17629: Cannot connect to the remote database server,首先排查网络问题是否通路,结果发现目标端防火墙是开着的, ...

  10. windows如何拉取一个文件夹下的所有文件名

    问题描述:遇到一个问题,是说一个文件夹下的文件太多了,如何去批量的拉去文件名呢,今天用CMD+DIR的方式拉取 1.文件目录也很深,就从文件导航栏进入CMD窗口 2.在当前目录中输入cmd,然后回车 ...