自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

移動(dòng)開發(fā)新利器 | 一文深入了解 Flutter 界面開發(fā)

新聞 Android
談到移動(dòng)端開發(fā),大家心中肯定會(huì)涌現(xiàn)出一系列名詞:iOS、Android、Weex,H5... 那為何還使用 Flutter?其實(shí),F(xiàn)lutter 通過自建繪制引擎,具備與 Native 媲美的性能指數(shù),且有很好的兩端一致性,因此 Flutter 提供了一種新的可選項(xiàng)。

 [[232114]]

  阿里妹導(dǎo)讀:談到移動(dòng)端開發(fā),大家心中肯定會(huì)涌現(xiàn)出一系列名詞:iOS、Android、Weex,H5... 那為何還使用 Flutter?其實(shí),F(xiàn)lutter 通過自建繪制引擎,具備與 Native 媲美的性能指數(shù),且有很好的兩端一致性,因此 Flutter 提供了一種新的可選項(xiàng)。閑魚寶貝詳情頁實(shí)踐上線也證明了這點(diǎn),可以在性能無損前提下降低 iOS&Android 開發(fā)成本。

  本文由閑魚技術(shù)團(tuán)隊(duì)出品。它將為你深入介紹 Flutter framework 關(guān)于視圖樹的創(chuàng)建與管理機(jī)制、布局、渲染的原理,以及 Flutter 布局與渲染相關(guān)性能優(yōu)化的設(shè)計(jì)思路的文章。同時(shí)介紹在使用 Flutter 開發(fā)過程中,遇到的一些坑和相應(yīng)的解決方案。

  Flutter 框架簡介

  1. 跨平臺(tái)應(yīng)用的框架,沒有使用 WebView 或者系統(tǒng)平臺(tái)自帶的控件,使用自身的高性能渲染引擎(Skia)自繪。

  2. 界面開發(fā)語言使用 dart,底層渲染引擎使用C, C++。

  3. 組合大于繼承,控件本身通常由許多小型、單用途的控件組成,結(jié)合起來產(chǎn)生強(qiáng)大的效果,類的層次結(jié)構(gòu)是扁平的,以***化可能的組合數(shù)量。

  Rendering Pipeline

  本文主要介紹 build、layout、paint 的三個(gè)階段。

  視圖樹

  Widget&Element&RenderObject

  Flutter 視圖樹包含了三種樹,上圖只是介紹了三顆樹的基礎(chǔ) class 的對(duì)應(yīng)關(guān)系和功能介紹。

  創(chuàng)建樹

  1. 創(chuàng)建 widget 樹

  2. 調(diào)用 runApp (rootWidget),將 rootWidget 傳給 rootElement,做為 rootElement 的子節(jié)點(diǎn),生成 Element 樹,由 Element 樹生成 Render 樹

  • Widget:存放渲染內(nèi)容、視圖布局信息,widget 的屬性***都是 immutable (如何更新數(shù)據(jù)呢?查看后續(xù)內(nèi)容)

  • Element:存放上下文,通過 Element 遍歷視圖樹,Element 同時(shí)持有 Widget 和 RenderObject

  • RenderObject:根據(jù) Widget 的布局屬性進(jìn)行 layout,paint Widget 傳人的內(nèi)容

  更新樹

  ★為什么 widget 都是 immutable?

  Flutter 界面開發(fā)是一種響應(yīng)式編程,主張 simple is fast,F(xiàn)lutter 設(shè)計(jì)的初衷希望數(shù)據(jù)變更時(shí)發(fā)送通知到對(duì)應(yīng)的可變更節(jié)點(diǎn)(可能是一個(gè) StatefullWidget 子節(jié)點(diǎn),也可以是 rootWidget),由上到下重新 create widget 樹進(jìn)行刷新,這種思路比較簡單,不用關(guān)心數(shù)據(jù)變更會(huì)影響到哪些節(jié)點(diǎn)。

  ★widget 重新創(chuàng)建,element 樹和 renderObject 樹是否也重新創(chuàng)建?

  widget 只是一個(gè)配置數(shù)據(jù)結(jié)構(gòu),創(chuàng)建是非常輕量的,加上 Flutter 團(tuán)隊(duì)對(duì) widget 的創(chuàng)建/銷毀做了優(yōu)化,不用擔(dān)心整個(gè) widget 樹重新創(chuàng)建所帶來的性能問題,但是 renderobject 就不一樣了,renderobject 涉及到 layout、paint 等復(fù)雜操作,是一個(gè)真正渲染的 view,整個(gè) view 樹重新創(chuàng)建開銷就比較大,所以答案是否定的?! ?strong> 

  ★樹的更新規(guī)則

  1. 找到 widget 對(duì)應(yīng)的 element 節(jié)點(diǎn),設(shè)置 element 為 dirty,觸發(fā) drawframe, drawframe 會(huì)調(diào)用 element 的 performRebuild ()進(jìn)行樹重建

  2. widget.build () == null, deactive element.child,刪除子樹,流程結(jié)束

  3. element.child.widget == NULL, mount 的新子樹,流程結(jié)束

  4. element.child.widget == widget.build () 無需重建,否則進(jìn)入流程5

  5. Widget.canUpdate (element.child.widget, newWidget) == true,更新 child 的 slot,element.child.update (newWidget)(如果 child 還有子節(jié)點(diǎn),則遞歸上面的流程進(jìn)行子樹更新),流程結(jié)束,否則轉(zhuǎn)6

  6. Widget.canUpdate (element.child.widget, newWidget) != true(widget 的 classtype 或者 key 不相等),deactivew element.child,mount 新子樹

  注意事項(xiàng):

  1. element.child.widget == widget.build (),不會(huì)觸發(fā)子樹的 update,當(dāng)觸發(fā) update 的時(shí)候,如果沒有生效,要注意 widget 是否使用舊 widget,沒有 new widget,導(dǎo)致 update 流程走到該 widget 就停止了。

  2. 子樹的深度變化,會(huì)引起子樹重建,如果子樹是一個(gè)復(fù)雜度很高的樹,可以使用 GlobalKey 做為子樹 widget 的 key。GlobalKey 具有緩存功能。

  ★如何觸發(fā)樹更新

  1. 全局更新:調(diào)用 runApp (rootWidget),一般 flutter 啟動(dòng)時(shí)調(diào)用后不再會(huì)調(diào)用。

  2. 局部子樹更新, 將該子樹做 StatefullWidget 的一個(gè)子 widget,并創(chuàng)建對(duì)應(yīng)的 State 類實(shí)例,通過調(diào)用 state.setState () 觸發(fā)該子樹的刷新。

  Widget

  StatefullWidget vs StatelessWidget

  1. StatelessWidget:無中間狀態(tài)變化的 widget,需要更新展示內(nèi)容就得通過重新 new,F(xiàn)lutter 推薦盡量使用 StatelessWidget。

  2. StatefullWidget:存在中間狀態(tài)變化,那么問題來了,widget 不是都 immutable 的,狀態(tài)變化存儲(chǔ)在哪里?Flutter 引入 state 的類用于存放中間態(tài),通過調(diào)用 state.setState ()進(jìn)行此節(jié)點(diǎn)及以下的整個(gè)子樹更新。

  State 生命周期  

  1. initState (): state create 之后被 insert 到 tree 時(shí)調(diào)用的

  2. didUpdateWidget (newWidget):祖先節(jié)點(diǎn) rebuild widget 時(shí)調(diào)用

  3. deactivate ():widget 被 remove 的時(shí)候調(diào)用,一個(gè) widget 從 tree 中 remove 掉,可以在 dispose 接口被調(diào)用前,重新 instert 到一個(gè)新 tree 中

  4. didChangeDependencies ():

  5. 初始化時(shí),在 initState ()之后立刻調(diào)用

  6. 當(dāng)依賴的 InheritedWidget rebuild,會(huì)觸發(fā)此接口被調(diào)用

  7. build ():

  8. After calling [initState].

  9. After calling [didUpdateWidget].

  10. After receiving a call to [setState].

  11. After a dependency of this [State] object changes (e.g., an[InheritedWidget] referenced by the previous [build] changes).

  12. After calling [deactivate] and then reinserting the [State] object into the tree at another location.

  13. dispose ():Widget 徹底銷毀時(shí)調(diào)用

  14. reassemble (): hot reload 調(diào)用

  注意事項(xiàng):

  1. A頁面 push 一個(gè)新的頁面B,A頁面的 widget 樹中的所有 state 會(huì)依次調(diào)用 deactivate (), didUpdateWidget (newWidget)、build ()(這里懷疑是 bug,A頁面 push 一個(gè)新頁面,理論上并沒有將A頁面進(jìn)行 remove 操作),當(dāng)然從功能上,沒有看出來有什么異常。

  2. 當(dāng) ListView 中的 item 滾動(dòng)出可顯示區(qū)域的時(shí)候,item 會(huì)被從樹中 remove 掉,此 item 子樹中所有的 state 都會(huì)被 dispose,state 記錄的數(shù)據(jù)都會(huì)銷毀,item 滾動(dòng)回可顯示區(qū)域時(shí),會(huì)重新創(chuàng)建全新的 state、element、renderobject。

  3. 使用 hot reload 功能時(shí),要特別注意 state 實(shí)例是沒有重新創(chuàng)建的,如果該 state 中存在一下復(fù)雜的資源更新需要重新加載才能生效,那么需要在 reassemble ()添加處理,不然當(dāng)你使用 hot reload 時(shí)候可能會(huì)出現(xiàn)一些意想不到的結(jié)果,例如,要將顯示本地文件的內(nèi)容到屏幕上,當(dāng)你開發(fā)過程中,替換了文件中的內(nèi)容,但是 hot reload 沒有觸發(fā)重新讀取文件內(nèi)容,頁面顯示還是原來的舊內(nèi)容。

  數(shù)據(jù)流轉(zhuǎn)

  ★從上往下

  數(shù)據(jù)從根往下傳數(shù)據(jù),常規(guī)做法是一層層往下,當(dāng)深度變大,數(shù)據(jù)的傳輸變的困難,F(xiàn)lutter 提供 InheritedWidget 用于子節(jié)點(diǎn)向祖先節(jié)點(diǎn)獲取數(shù)據(jù)的機(jī)制,如下例子:

  child 及其以下的節(jié)點(diǎn)可以通過調(diào)用下面的接口讀取 color 數(shù)據(jù):

  說明:BuildContext 就是 Element 的一個(gè)接口類

  context.inheritFromWidgetOfExactType (FrogColor)其實(shí)是通過 context/element 往上遍歷樹,查找到***個(gè) FrogColor 的祖先節(jié)點(diǎn),取該節(jié)點(diǎn)的 widget 對(duì)象。

  ★從下往上

  子節(jié)點(diǎn)狀態(tài)變更,向上上報(bào)通過發(fā)送通知的方式

  • 定義通知類,繼承至 Notification

  • 父節(jié)點(diǎn)使用 NotificationListener 進(jìn)行監(jiān)聽捕獲通知

  • 子節(jié)點(diǎn)有數(shù)據(jù)變更調(diào)用下面接口進(jìn)行數(shù)據(jù)上報(bào)

  ★閑魚 Flutter 的界面框架設(shè)計(jì)

  

   

  Layout   

  ★Size 計(jì)算

  parent 傳入約束條件,在 dramframe 的 layout 階段,child 根據(jù)自身的渲染內(nèi)容返回 size。

  問題:在 build ()階段獲取不到 size,很多時(shí)候需要提前知道部分 widget size 來進(jìn)行布局,解決方案當(dāng) widget 在對(duì)應(yīng) renderobject 的 layout 階段之后,發(fā)送一個(gè) LayoutChangeNotification,參考 SizeChangedLayoutNotifier class,但是 SizeChangedLayoutNotifier 沒有上報(bào) init layout size,可以自己參考這個(gè)實(shí)現(xiàn)封裝一個(gè) Notifier。

  ★Offset 計(jì)算

  1. renderObject 拿到計(jì)算好的 size,再加上一些布局屬性(align、paddig)等,計(jì)算 child 相對(duì) parent 的 offset。

  2. offset 存放在每個(gè) child renderObject 的 BoxParentData 中。

  3. 當(dāng) parent 擁有 mutil children 時(shí),BoxParentData 還用來存 children 兄弟節(jié)點(diǎn)之間的遍歷順序?! ?strong> 

  ★Relayout boundary

  renderObject 在 layout 階段做了 Relayout boundary 的優(yōu)化,當(dāng)子樹進(jìn)行 relayout 時(shí),滿足下面三種中的一種:

  • parentUsesSize == false

  • sizedByParent == true

  • constraints.isTight

  那么該 renderObject 設(shè)置為 Relayout boundary,也就是該 renderObject 的重新 layout 不觸發(fā) parent 的 layout,一般情況下開發(fā)人員不需要關(guān)心 Relayout boundary,除非是使用 CustomMultiChildLayout。

  Paint

  ★L(fēng)ayer

  iOS 的每一個(gè) UIView 都有一個(gè) layer,F(xiàn)lutter 的 render object 不一定存在 layer,一般情況下一個(gè) renderObject 子樹都渲染在一個(gè) layer 上,那么什么 renderObject 具有 layer,子 renderObject 怎么渲染到這個(gè) layer?

  1. 當(dāng)一個(gè) renderObject 的
或者


,renderOject 會(huì)有對(duì)應(yīng)的 compositing layer。

  2. 子 renderObject 會(huì)對(duì)目標(biāo) layer 返回對(duì)應(yīng)的 offsetLayer, 目標(biāo) compositing layer 再根據(jù) offset 合成一個(gè)渲染的紋理 buffer。

  ★Repaint Boundary

  類似 Relayout boundary,Paint 階段也有 Repaint Boundary,目的和 layout 一樣,就是對(duì)應(yīng)子樹的 paint 不會(huì)導(dǎo)致外部的 repaint,但是 Relayout boundary 需要開發(fā)人員自己設(shè)置,使用 RepaintBoundary widget 進(jìn)行設(shè)置,ListView 在渲染的 item 默認(rèn)都是使用了 RepaintBoundary,顯而易見 ListView 的 children 之間都是相互獨(dú)立的。Flutter 建議復(fù)雜的 image 渲染使用 RepaintBoundary,image 的渲染需要 io 操作,然后解碼,***渲染,使用 RepaintBoundary 可以進(jìn)行 gpu 的緩存,但是不一定就會(huì)緩存,engine 會(huì)判斷這個(gè) image 是否足夠復(fù)雜,畢竟 gpu 緩存還是非常珍貴的,同時(shí) RepaintBoundary 還會(huì)對(duì)一些反復(fù)渲染的 layer 進(jìn)行緩存處理(反復(fù)渲染 3 次及以上,這個(gè)是 Flutter 的視頻中提到的)。

  結(jié)語

  Flutter 還處于 Beta 階段,有些界面編程的接口設(shè)計(jì)還不夠成熟,相比 iOS 和安卓生態(tài)還很不成熟,需要我們共同的創(chuàng)建,F(xiàn)lutter 提供的調(diào)試工具相比一開始接觸的時(shí)候,已經(jīng)完善很多,讓我們給 Flutter 更多的耐心和包容,期待 Flutter 越來越完善。

  參考資料

責(zé)任編輯:張燕妮 來源: 阿里技術(shù)
相關(guān)推薦

2021-01-27 11:10:49

JVM性能調(diào)優(yōu)

2019-11-28 09:33:08

Redis架構(gòu)互聯(lián)網(wǎng)

2019-07-09 08:29:51

TCPIP協(xié)議

2019-11-20 10:07:07

Redis數(shù)據(jù)系統(tǒng)

2018-04-25 10:13:30

Redis內(nèi)存模型

2023-03-31 08:16:53

Flutter優(yōu)化內(nèi)存管理

2022-02-28 10:30:03

架構(gòu)代碼Native

2020-09-18 06:42:14

正則表達(dá)式程序

2024-04-30 11:11:33

aiohttp模塊編程

2015-10-21 10:42:33

技術(shù)周刊

2020-08-27 07:34:50

Zookeeper數(shù)據(jù)結(jié)構(gòu)

2023-12-08 17:59:55

工具Git LFS管理

2020-07-20 06:35:55

BashLinux

2010-11-19 16:22:14

Oracle事務(wù)

2010-07-13 09:36:25

2010-06-23 20:31:54

2009-08-25 16:27:10

Mscomm控件

2022-08-26 13:48:40

EPUBLinux

2020-09-21 09:53:04

FlexCSS開發(fā)

2022-03-08 13:42:13

橫向移動(dòng)網(wǎng)絡(luò)攻擊
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)