Flutter调优--深入探究MediaQuery引起界面Rebuild的原因及解决办法
前言
我们可以通过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方法,主界面对应的Element
和MediaQuery
建立了绑定关系,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
对应的Element
的updateChild
方法,简单看下updateChild
的内部处理规则:
对MediaQueryFromWindow而言,每次都会创建新的MediaQuery Widget,根据Element#updateChild源码(不是本文讨论重点,不再详细分析其源码)得知,最终会调用MediaQuery对应的Element的update方法。
经过一系列的跳转过后,最终会调用到下面的两个核心方法:
上面介绍的MediaQuery.of(context)
方法最终会把入参Context
放到_dependents
变量里,而这里会遍历这个map
,调用每一个Context
的didChangeDependecies
方法,didChangeDependecies
会将此Context
置为dirty状态,下一帧来临时会被重新绘制,并调用此Context
的build
方法。
所以,破案了,当键盘弹起/隐藏时快递主页会被rebuild的原因找到了!
整体的rebuild调用流程如下,感兴趣的可以结合这个调用流程图去看源码:
避免rebuild的办法
研究过源码后,解决方案就变的很简单。
- 自定义
useInheritedMediaQuery
属性为true,并在最外面包一层MediaQuery
,让WidgetsApp
创建时使用MediaQuery
,而不去使用监听了application尺寸变化的_MediaQueryFromWindow
控件。
- 避免在页面中使用
MediaQuery.of(context)
方法,可以使用对应的替代方法,比如本例可以采用下面的代码进行替代,注意单位的转换。
- 如果必须要使用
MediaQuery.of(context)
方法,可以使用Builder
控件包裹下,of方法的入参传入此Builder
的context
即可,这样被rebuild仅是Builder
控件包裹下的widget子树。
总结
app界面逐渐复杂时,我们不得不考虑去优化界面性能。本文中介绍的例子在开发中是很常见的,如果不了解MediaQuery.of的机制,可能会引起大量使用此方法的界面发生重绘操作,造成页面卡顿、帧率下降。我们详细分析了背后的源码逻辑,介绍了解决办法,希望能给大家的调优工作提供些许帮助。
作者:京东物流 沈明亮
来源:京东云开发者社区
Flutter调优--深入探究MediaQuery引起界面Rebuild的原因及解决办法的更多相关文章
- qt designer启动后不显示界面问题的原因与解决办法
Qt 5.6.1无论是在vs里双击ui文件还是直接启动designer.exe都一直无法显示界面,但任务管理器中可以看到该进程是存在的.前几天还正常的,但昨天加了一块NVIDIA的显卡(机器自带核显) ...
- Qt拖拽界面 (*.ui) 缩放问题及解决办法
问题 使用Qt Designer 设计的界面,在缩放的时候不能随着主窗口一起缩放. 解决办法 之前遇到这个问题的时候,都是直接重写resizeEvent接口来实现的,在自动生成的Ui_Widget或U ...
- Qt拖拽界面 (*.ui) 缩放问题及解决办法(在最顶层放一个Layout)
问题 使用Qt Designer 设计的界面,在缩放的时候不能随着主窗口一起缩放. 解决办法 之前遇到这个问题的时候,都是直接重写resizeEvent接口来实现的,在自动生成的Ui_Widget或U ...
- ctrl+alt+F1~6进入不了字符界面,黑屏的解决办法
ubuntu系统,我是ubuntu14.04 本来想装cuda,需要在字符界面下装,奈何按ctrl+alt+F1就黑屏了,按ctrl+alt+F7又可以正常回到图形界面,网上查了很多,有的方法也试过, ...
- 分享一个android debug模式,出现 waiting for debugger把界面卡住,取巧的解决办法
使用android studio开发程序时,有时会出现 waiting for debugger 卡住界面,软件无法正常debug运行的情况,很多网友分享了一些解决办法,比如: 1 打开cmd进入命令 ...
- cloudera manager的7180 web界面访问不了的解决办法(图文详解)
说在前面的话 我的机器是总共4台,分别为ubuntucmbigdata1.ubuntucmbigdata2.ubuntucmbigdata3和ubuntucmbigdata4.(注意啦,以下是针对Ub ...
- 安装Windows 10后PDF补丁丁等程序界面变得模糊的解决办法
对于使用高分辨率屏幕且屏幕缩放比例在 100%以上的用户,升级到 Windows 10 后将发现许多程序的界面,例如QQ.电脑管家.Windows本身的服务管理程序等等,都变得非常模糊,<PDF ...
- 微信SDK登录无法调起,微信SDK无法接收回调的几种解决办法
今天有位同事请求帮忙调试微信登录问题,他遇到了以下2个问题,所以,写篇日志备忘,如果有其它朋友遇到此类问题,都可以照此解决! 平时在开发中,有些开发者经常会遇到微信登录SDK登录时,无法调起微信客户端 ...
- activemq无法启动且后台管理界面进不去的解决办法
从官网下载了一个最新的activemq,目前最新版本是5.14.5 我下载的是windows版本,通过执行%activemq home%/bin/win64/InstallService.bat,可以 ...
- 【Spark深入学习 -14】Spark应用经验与程序调优
----本节内容------- 1.遗留问题解答 2.Spark调优初体验 2.1 利用WebUI分析程序瓶颈 2.2 设置合适的资源 2.3 调整任务的并发度 2.4 修改存储格式 3.Spark调 ...
随机推荐
- KMP 自动机,孤独的自动机(同时也是CF1721E的题解)
给定字符串 \(s\),以及 \(q\) 个串 \(t_i\),求将 \(s\) 分别与每个 \(t_i\) 拼接起来后,最靠右的 \(|t_i|\) 个前缀的 border 长度.询问间相互独立. ...
- Centos Linux 设置 jar 包 开机自启动
1.设置jar包可执行权限 点击查看代码 mkdir /usr/java cd /usr/java chmod 777 xxx.jar 2.编写脚本文件 touch xxx.sh 将文件放置到 /us ...
- java选择结构-switch
java选择结构-switch java的另一个多选择结构switch-case case中的value为常数值. 不加break,会一直执行到最后,包括default(case穿透) switch( ...
- Django笔记六之外键ForeignKey介绍
这一篇笔记介绍 Django 系统 model 的外键处理,ForeignKey 以及相应的处理方法. 这是一种一对多的字段类型,表示两张表之间的关联关系. 本篇笔记的目录如下: on_delete ...
- flutter widget---->Spacer
如果你想灵活控制Flex容器(Row, Column)中子组件中的间隔,可以考虑使用Spacer.下面以Row为例子,来为它的子组件添加间距. use Spacer import 'package:f ...
- 团队如何选择合适的Git分支策略?
现代软件开发过程中要实现高效的团队协作,需要使用代码分支管理工具实现代码的共享.追溯.回滚及维护等功能.目前流行的代码管理工具,包括CVS,SVN,Git,Mercurial等. 相比CVS和SVN的 ...
- LeeCode 二叉树问题(二)
二叉树的构建 LeeCode 106: 从中序遍历与后续遍历序列构造二叉树 题目描述 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, posto ...
- LeeCode 942 增减字符串匹配
LeeCode 942 题目描述: 由范围 [0,n] 内所有整数组成的 n+1 个整数的排列序列可以表示为长度为 n 的字符串 s ,其中: 如果 perm[i] < perm[i + 1] ...
- ASP.NET Core - 缓存之内存缓存(下)
话接上篇 [ASP.NET Core - 缓存之内存缓存(上)],所以这里的目录从 2.4 开始. 2.4 MemoryCacheEntryOptions MemoryCacheEntryOption ...
- 记一次 .NET某医疗器械清洗系统 卡死分析
一:背景 1. 讲故事 前段时间协助训练营里的一位朋友分析了一个程序卡死的问题,回过头来看这个案例比较经典,这篇稍微整理一下供后来者少踩坑吧. 二:WinDbg 分析 1. 为什么会卡死 因为是窗体程 ...