Flutter調優--深入探究MediaQuery引起界面Rebuild的原因及解決辦法

来源:https://www.cnblogs.com/jingdongkeji/archive/2023/05/29/17440872.html
-Advertisement-
Play Games

app界面逐漸複雜時,我們不得不考慮去優化界面性能。本文中介紹的例子在開發中是很常見的,如果不瞭解MediaQuery.of的機制,可能會引起大量使用此方法的界面發生重繪操作,造成頁面卡頓、幀率下降。我們詳細分析了背後的源碼邏輯,介紹瞭解決辦法,希望能給大家的調優工作提供些許幫助。 ...


前言

我們可以通過MediaQuery.of(context)方法獲取到一些設備和系統的相關信息,比如狀態欄的高度、當前是否是黑暗模式等等,使用起來相當方便,但是也要註意可能引起的頁面rebuild問題。本文會介我們可以通過MediaQuery.of(context)方法獲取到一些設備和系統的相關信息,比如狀態欄的高度、當前是否是黑暗模式等等,使用起來相當方便,但是也要註意可能引起的頁面rebuild問題。本文會介紹一個典型的例子,並深入源碼來探討引起rebuild的原因,最後介紹避免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的機制,可能會引起大量使用此方法的界面發生重繪操作,造成頁面卡頓、幀率下降。我們詳細分析了背後的源碼邏輯,介紹瞭解決辦法,希望能給大家的調優工作提供些許幫助。

作者:京東物流 沈明亮

來源:京東雲開發者社區


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我記得之前我採集、提取過兩份菜譜資料資料庫,一份是菜譜數據不上萬的帶圖片的,詳情點擊這裡,一份是2萬多條記錄但格式未整理並且沒有圖片的。而今天的這個3萬多條含圖片的菜譜資料ACCESS資料庫包含了3萬多條常菜譜做法,並且結構簡潔,最主要的是每個菜譜都包含圖片。 菜譜分50個大類和400多種小類,大類 ...
  • 一個學語文的朋友問我有沒有關於中華典故以及萬物由來的數據,我看了看手頭的資料發現還真沒有,而且網上似乎也沒有這一類的成品,因此就用程式採集了一個中華典故網的文章。 分類統計情況有:成語典故(共4198條)、典故雜聞(共702條)、國外典故(共29條)、科學典故(共29條)、歷史典故(共374條)、神 ...
  • 本系列為:MySQL資料庫詳解,為千鋒資深教學老師獨家創作,致力於為大家講解清晰MySQL資料庫相關知識點,含有豐富的代碼案例及講解。如果感覺對大家有幫助的話,可以【關註】持續追更 ...
  • 《公務員考試基礎知識題庫ACCESS資料庫》搜集了大量公務員考試試題,包括公共基礎知識試題、行政職業能力試題、法律基礎知識試題、公安基礎知識試題等。 分類記錄統計情況為:法律基礎知識試題(共1359條)、公安基礎知識試題(共1709條)、公共基礎知識試題(共2905條)、行政職業能力試題(共6613 ...
  • 《求職面試寶典大全ACCESS資料庫》包含:1-穿著打扮、2-隨身攜帶、3-場景指導、4-考官分析、5-考官問題、6-回答參考表、7-面試大全。雖然有些表的記錄數不多,但勝在信息全面以及考官問題及答案的全面。 問題類型記錄數為:待遇問題(10條)、個人素養(10條)、教育家庭(10條)、經驗問題(1 ...
  • 因為簽於網站笑話不是採用微博型(一句或兩句短篇可以採用250長度的文本型存儲),而是所以文章內容型,所以內容保存的欄位是 Microsoft Access 資料庫里的備註類型。 資料庫內容經過整理,格式比較統一,比如不會有多餘的段落或不整齊的段落;分類比較集中,只有爆笑男女(305)、兒童笑話(59 ...
  • [TOC](快速上手kettle二 小試牛刀) ### 一 、前言 上一期中大概介紹了下kettle,並已經把kettle下載安裝完了。 這一期我們就來簡單體驗下kettle怎麼進行數據轉換的。 ### 二 、兩個小目標 我們這裡呢就以兩個小案例來體驗下kettle - 將csv文件通過kettle ...
  • 這是從互聯網上搜集的各種初中語文作文20000篇,經過完美的格式內容整理,包含大量《中考滿分作文》及《中考作文指導》資料,資料庫按年級和分類可以快速的為初中作文提供最齊全的參考。 年級包含:初中二年級(2377)、初中三年級(1988)、初中一年級(15465)。分類包含:讀後感(778)、話題(1 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...