Flutter调优--深入探究MediaQuery引起界面Rebuild的原因及解决办法 | 京东云技术团队

news2024/12/26 23:13:48

前言

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

典型例子

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

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

1c71b25e3294787a554ff654302a1950b05f1e2c.png

主界面的build代码如下:

0e60e8a0008a952db4058f2f6c7c7f4d2156d1bf.png

源码探究

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

MediaQuery

ca93e1de11acadcdacc1be8bf1621c53ab4d4e67.png

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

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

fromWindow(key : Key, child : Widget)

5b724d4ff9ae1364473ce47a81a5c0c4fcd247ef.png

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

of(context : BuildContext)

50.png

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

MediaQuery.of(context) 调用流程

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

845592be3d798d7f664f343d1c3bc2e28b2ed114.png

dependOnInheritedWidgetOfExactType

0921532a03e10a237cce55d11b688cac76e3744e.png

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

dependOnInheritedElement

3475047fddc5392b520ed244eaf9bde411a21349.png

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

updateDependencies

fc0db47c49a1f82bde4797e316087c40bda77d35.png

setDependencies

a33b8a6c17284ec860c808104a5e9b5796a3e247.png

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

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

Rebuild起点

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

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

b99a11825cca09b3db11d7d08c0f2f7e97b1b83a.png

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

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

628215f6ff6f55a7e4253fd82aec42af96564435.png

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

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

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

a33b8a6c17284ec860c808104a5e9b5796a3e247.png

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

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

beb27e10f74c988d10e0de4fda450f52aec77be7.png

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

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

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

bf648f2c2399def55a9fdb1a20260ef2fe858394.png

避免rebuild的办法

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

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

f048696ba22681d3cefab511748656b38a3d1098.png

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

17ebf14ed1e437106cd05b5c17f2da2aeb815213.png

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

5e9bafc27455cbba7c69653d1052f73c1ed71fa5.png

总结

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

作者:京东物流 沈明亮

来源:京东云开发者社区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/583178.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:用Python下载MP3的方法

用Python下载MP3的方法 如果你想从互联网上下载MP3,那么你可以使用Python来实现这个任务。在本文中,我们将介绍如何用Python编写程序来下载MP3,同时还将分享一些有用的工具和资源。 Python中使用的库 要下载MP3,你需要使用Pyth…

接口测试简介以及接口测试用例设计思路

接口测试简介 1.什么是接口 接口就是内部模块对模块,外部系统对其他服务提供的一种可调用或者连接的能力的标准,就好比usb接口,他是系统向外接提供的一种用于物理数据传输的一个接口,当然仅仅是一个接口是不能进行传输的&#x…

如何快速入门高性能计算?五个实用学习建议分享

高性能计算 (High-Performance Computing,HPC)是指通过计算机技术实现高速运算,对大规模科学计算、数据分析、复杂制造等领域的研究与应用提供支撑。目前,HPC已经广泛应用于医学、天文、材料科学、地球科学、气象科学、…

【cmake 学习】cmake判断当前所处系统(CMAKE_SYSTEM_NAME)

目录 1、使用内置变量判断 2、使用变量 CMAKE_SYSTEM_NAME 判断 1、使用内置变量判断 cmake 内置了许多用于表示系统的变量,如 UNIX、WIN32、LINUX、IOS等。假设当前系统为Linux系统,那么 LINUX 变量的值为 1。 更多变量可以参考:cmake-v…

最近距离 | EXCEL中批量查找与原点最近距离的目标点

一 需求 企业送餐、物流、通勤、选址等经常会遇到这样的需求,仓储地是固定的,客户下单后由于客户的地址具有随机性,需按照地理位置分布结合仓储位置进行分析,按距离近及远合理分配仓储,以便节省配送成本。 本文就以这…

深度学习AI编译器-MLIR简介

1、什么是MLIR MLIR:Multi-Level Intermediate Representation 多级中间表达 LLVM下的子项目编译器基础框架统一IR格式,提高通用性和可复用性自带Tensor类型,目前主要用于深度学习领域 中间表达 IR(Intermediate Representatio…

Day55【动态规划】392.判断子序列、115.不同的子序列

392.判断子序列 力扣题目链接/文章讲解 视频讲解 本题目可以用双指针法来做 class Solution { public:bool isSubsequence(string s, string t) {// pointer to s, pointer to tint ps 0, pt 0; for (pt 0; pt < t.size(); pt) { // 遍历t&#xff0c;在t中按顺序寻找…

Kali-linux Gerix Wifi Cracker破解无线网络

Gerix Wifi Cracker是另一个aircrack图形用户界面的无线网络破解工具。本节将介绍使用该工具破解无线网络及创建假的接入点。 9.3.1 Gerix破解WEP加密的无线网络 在前面介绍了手动使用Aircrack-ng破解WEP和WPA/WPA2加密的无线网络。为了方便&#xff0c;本小节将介绍使用Geri…

数字图像处理①基于ADMM的全变分去噪算法

文章目录 1. Problem2. 仿真结果3. MATLAB算法4. 源码地址参考文献 1. Problem 在图像处理中&#xff0c;图像信号总会因为各种原因受到噪声的干扰&#xff0c;其中高斯噪声就是典型的干扰类型之一。 针对图像去噪的模型有很多种&#xff0c;其中全变分模型被认为是最有效的模…

北醒Modbus协议在Python下实现功能配置

目录 实验目的测试环境Python库需求Benewake(北醒) TF雷达接口及通讯协议说明接口定义Modbus通信协议说明功能码说明 接线示意图库安装说明例程运行与测试 实验目的 实现485接口系列雷达Modbus协议在Python上实现功能配置。 本例程界面分为主菜单、测距子菜单、配置子菜单&…

社区团购-v.1.6.0更新

likeshop社区团购系统发布新版本1.6.0&#xff0c;主要更新如下&#xff1a; 新增&#xff1a; 小程序-登录引导用户填写头像和昵称 小程序-热更新代码弹窗 后台-正版检测、版本检测 后台-页面装修支持拖拽排序 后台-订单管理增加导出功能 修复&#xff1a; 后台-关联团…

本地化部署AI语言模型RWKV指南,ChatGPT顿时感觉不香了。

之前由于ChatGpt处处受限&#xff0c;又没法注册的同学们有福了&#xff0c;我们可以在自己电脑上本地化部署一套AI语言模型&#xff0c;且对于电脑配置要求也不是非常高&#xff0c;对它就是RWKV。 关于RWKV RWKV是一个开源且允许商用的大语言模型&#xff0c;灵活性很高且极…

Linux之搭建环境

文章目录 1 FileZilla软件2 Linux搭建samba文件共享服务器&#xff0c;实现基于Linux和Windows的共享文件服务2.1 smaba的安装与基本应用2.2 samba的账号权限配置 1 FileZilla软件 在跟着正点原子教程安装后&#xff0c;出现如下问题 解决方法如下 参考文章解决FileZilla连接…

游戏配音怎么弄的?分享三个游戏配音制作方法

随着时代的发展&#xff0c;人们对于配音的要求也越来越高&#xff0c;除了传统的文字配音外&#xff0c;现在又出现了游戏配音。其实游戏配音也是有一定门槛的&#xff0c;并不是人人都可以做得好的。但是如果你想要拥有一位自己喜欢的游戏角色&#xff0c;那么你就要学会游戏…

MyBatis 快速入门(上)

文章目录 一. MyBatis 是什么?二. 学习 MyBatis 的意义三. MyBatis 学习四. 创建 Mybatis 查询4.1 创建数据库和表4. 2 MyBatis 环境搭建1. 添加 MyBatis 框架支持image-202305052040340772. 设置 MyBatis 的配置信息2.1 设置数据库连接的相关信息2.2 MyBatis 的xml 保存路径 …

如何白嫖可联网GPT- NEW Bing

这里写自定义目录标题 前沿方法安装总结 前沿 目前我们想体验可联网的GPT只能去OPENAI官网购买plus会员&#xff0c;一个月20刀&#xff0c;换算一下的话大概在140左右&#xff0c;当然由于OPENAI某些原因经常封禁不符合规定的账号&#xff0c;那么有没有一种办法能白嫖快速的…

Qt Qml 实现键鼠长时间未操作锁屏

文章目录 摘要实现思路键盘鼠标监控百度到的方法我的自己方法 最后 关键字&#xff1a; Qt、 Qml、 QInputEvent 、 QStandardItem、 eventFilter 摘要 今日需求&#xff1a; 项目中需要实时检测用户是否长时间为操作键盘和鼠标&#xff0c;如果超过预设时间未操作键盘和…

Linux之管道

目录 Linux之管道 操作符号 作用 用法 管道符使用场合 匿名管道与命名管道的区别 如何创建命名管道 案例举例 案例1 --- 将/etc/passwd中的用户按UID大小排序 案例2 --- 统计出最占CPU的5个进程 案例3 --- 统计当前/etc/passwd中用户使用的shell类型 案例4 --- 统计网站…

Java大数据文件处理方法

前言 Java大数据文件处理是一种使用Java编写的大型数据处理技术&#xff0c;特别适用于处理大型数据文件。在这篇文章中&#xff0c;我们将了解什么是Java大数据文件处理&#xff0c;为什么它很重要以及Java大数据文件处理的方法和技术。 一、什么是Java大数据文件处理&#…

交易信号有效性测算1——N日涨跌幅

在交易信号发出后&#xff0c;我们需要一些程序化的流程&#xff0c;来验证信号的有效性&#xff0c;其中信号发出后N日的涨跌幅就是一个比较常见的任务 布林带交易策略 我们以布林带&#xff08;BOLL&#xff09;交易策略为示例&#xff1a; 中轨线 N日移动平均线上轨线 …