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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...