跨平臺開發的救星-讓我們來了解一下flutter
第一次看文章的朋友可以關注我,會不定期釋出Android面試內容、進階專題等等。
簡介
很多人已經用上了flutter,今天就來介紹一下
Flutter 架構
Flutter框架分三層
Framework,Engine, Embedder
Framework使用dart語言實現,包括UI,文字,圖片,按鈕等Widgets,渲染,動畫,手勢等。此部分的核心程式碼是flutter倉庫下的flutter package,以及sky_engine倉庫下的 io, async, ui(dart:ui庫提供了Flutter框架和引擎之間的介面)等package。
Engine使用C++實現,主要包括:Skia, Dart 和 Text。
- Skia是開源的二維圖形庫,提供了適用於多種軟硬體平臺的通用API。其已作為Google Chrome,Chrome OS,Android, Mozilla Firefox, Firefox OS等其他眾多產品的圖形引擎,支援平臺還包括Windows, macOS, iOS,Android,Ubuntu等。
- Dart 部分主要包括:Dart Runtime,Garbage Collection(GC),如果是Debug模式的話,還包括JIT(Just In Time)支援。Release和Profile模式下,是AOT(Ahead Of Time)編譯成了原生的arm程式碼,並不存在JIT部分。
- Text 即文字渲染,其渲染層次如下:衍生自 Minikin的libtxt庫(用於字型選擇,分隔行);HartBuzz用於字形選擇和成型;Skia作為渲染/GPU後端,在Android和Fuchsia上使用FreeType渲染,在iOS上使用CoreGraphics來渲染字型。
Embedder是一個嵌入層,透過該層把Flutter嵌入到各個平臺上去,Embedder的主要工作包括渲染Surface設定, 執行緒設定,以及外掛等。平臺(如iOS)只是提供一個畫布,剩餘的所有渲染相關的邏輯都在Flutter內部,這就使得它具有了很好的跨端一致性。
Dart語言
Dart 也是一種VM語言,所以在每個執行flutter的app中都有一個dart的執行環境。編譯模式支援AOT和JIT。
Dart最開始是google設計出來替代javascript的,但是並沒有湊效。後面Flutter選擇了Dart, 才使Dart活躍起來。
Dart語言的特點:
- 單程式非同步事件模型
- 強型別,可以型別推斷
- 具有極高的執行效率和優秀的程式碼執行最佳化的VM,根據早前的基準測試,效能比肩 Java7 的JVM;
- 獨特的隔離區( Isolate ),可以實現多執行緒
- 物件導向程式設計,一切資料型別均派生自 Object
- 運算子過載,泛型支援
- 強大的 Future 和 Stream 模型,可以簡單實現高效的程式碼
- Minix 特性,可以更好的實現方法複用
- 全平臺語言,可以很好的勝任移動和前後端的開發
- 在語法上,Dart 提供了很多便捷的操作
Flutter執行緒管理
Flutter Engine自己不建立, 管理執行緒。Flutter Engine執行緒的建立和管理是由embedder負責的
Embeder提供四個Task Runner, 每個Task Runner負責不同的任務,Flutter Engine不在乎Task Runner具體跑在哪個執行緒,但是它需要執行緒配置在整一個生命週期裡面保持穩定。也就是說一個Runner最好始終保持在同一執行緒執行
Platform Task Runner
是Flutter Engine的主Task Runner,執行Platform Task Runner的執行緒可以理解為是主執行緒。類似於Android Main Thread或者iOS的Main Thread。對於Flutter Engine來說Platform Runner所在的執行緒跟其它執行緒並沒有實質上的區別。 可以同時啟動多個Engine例項,每個Engine對應一個Platform Runner,每個Runner跑在各自的執行緒裡。這也是Fuchsia(Google正在開發的操作引擎)裡Content Handler的工作原理。一般情況下,一個Flutter應用啟動的時候會建立一個Engine例項,Engine建立的時候會建立一個執行緒供Platform Runner使用。
跟Flutter Engine的所有互動(介面呼叫)必須發生在Platform Thread,試圖在其它執行緒中呼叫Flutter Engine會導致無法預期的異常。這跟Android和IOS對於UI的操作都必須在主執行緒進行相類似。需要注意的是在Flutter Engine中有很多模組都是非執行緒安全的。一旦引擎正常啟動執行起來,所有引擎API呼叫都將在Platform Thread裡發生。
Platform Runner所在的Thread不僅僅處理與Engine互動,它還處理來自平臺的訊息。這樣的處理比較方便的,因為幾乎所有引擎的呼叫都只有在Platform Thread進行才能是安全的,Native Plugins不必要做額外的執行緒操作就可以保證操作能夠在Platform Thread進行。如果Plugin自己啟動了額外的執行緒,那麼它需要負責將返回結果派發回Platform Thread以便Dart能夠安全地處理。規則很簡單,對於Flutter Engine的介面呼叫都需保證在Platform Thread進行。
阻塞Platform Thread不會直接導致Flutter應用的卡頓(跟iOS android主執行緒不同)。儘管如此,平臺對Platform Thread還是有強制執行限制。所以建議複雜計算邏輯操作不要放在Platform Thread而是放在其它執行緒(不包括我們現在討論的這個四個執行緒)。其他執行緒處理完畢後將結果轉發回Platform Thread。長時間卡住Platform Thread應用有可能會被系統Watchdot強行殺死。
UI Task Runner
Flutter Engine用於執行Dart root isolate程式碼。Root isolate比較特殊,它繫結了不少Flutter需要的函式方法。Root isolate執行應用的main code。引擎啟動的時候為其增加了必要的繫結,使其具備排程提交渲染幀的能力。
- 對於每一幀,引擎要做的事情有:
- Root isolate通知Flutter Engine有幀需要渲染。
- Flutter Engine通知平臺,需要在下一個vsync的時候得到通知。
- 平臺等待下一個vsync
- 對建立的物件和Widgets進行Layout並生成一個Layer Tree,Layer Tree馬上被提交給Flutter Engine。當前階段沒有進行任何光柵化,這個步驟僅是生成了對需要繪製內容的描述。
- 建立或者更新Tree,這個Tree包含了用於螢幕上顯示Widgets的語義資訊。這個東西主要用於平臺相關的輔助Accessibility元素的配置和渲染。
除了渲染相關邏輯之外Root Isolate還是處理來自Native Plugins的訊息響應,Timers,MicroTasks和非同步IO。
Root Isolate負責建立管理的Layer Tree最終決定什麼內容要繪製到螢幕上。因此這個執行緒的過載會直接導致卡頓掉幀。
如果確實有無法避免的繁重計算,建議將其放到獨立的Isolate去執行,比如使用compute關鍵字或者放到非Root Isolate,這樣可以避免應用UI卡頓。但是需要注意的是非Root Isolate缺少Flutter引擎需要的一些函式繫結,你無法在這個Isolate直接與Flutter Engine互動。所以只在需要大量計算的時候採用獨立Isolate。
GPU Task Runner
用於執行裝置GPU的相關呼叫。UI Task Runner建立的Layer Tree資訊是平臺不相關,也就是說Layer Tree提供了繪製所需要的資訊,具體如何實現繪製取決於具體平臺和方式,可以是OpenGL,Vulkan,軟體繪製或者其他Skia配置的繪圖實現。GPU Task Runner中的模組負責將Layer Tree提供的資訊轉化為實際的GPU指令。GPU Task Runner同時也負責配置管理每一幀繪製所需要的GPU資源,這包括平臺Framebuffer的建立,Surface生命週期管理,保證Texture和Buffers在繪製的時候是可用的。
基於Layer Tree的處理時長和GPU幀顯示到螢幕的耗時,GPU Task Runner可能會延遲下一幀在UI Task Runner的排程。一般來說UI Runner和GPU Runner跑在不同的執行緒。存在這種可能,UI Runner在已經準備好了下一幀的情況下,GPU Runner卻還正在向GPU提交上一幀。這種延遲排程機制確保不讓UI Runner分配過多的任務給GPU Runner。
GPU Runner可以導致UI Runner的幀排程的延遲,GPU Runner的過載會導致Flutter應用的卡頓。一般來說使用者沒有機會向GPU Runner直接提交任務,因為平臺和Dart程式碼都無法跑進GPU Runner。但是Embeder還是可以向GPU Runner提交任務的。因此建議為每一個Engine例項都新建一個專用的GPU Runner執行緒。
IO Task Runner
主要功能是從圖片儲存(比如磁碟)中讀取壓縮的圖片格式,將圖片資料進行處理為GPU Runner的渲染做好準備。在Texture的準備過程中,IO Runner首先要讀取壓縮的圖片二進位制資料(比如PNG,JPEG),將其解壓轉換成GPU能夠處理的格式然後將資料上傳到GPU。這些複雜操作如果跑在GPU執行緒的話會導致Flutter應用UI卡頓。但是隻有GPU Runner能夠訪問GPU,所以IO Runner模組在引擎啟動的時候配置了一個特殊的Context,這個Context跟GPU Runner使用的Context在同一個ShareGroup。事實上圖片資料的讀取和解壓是可以放到一個執行緒池裡面去做的,但是這個Context的訪問只能在特定執行緒才能保證安全。這也是為什麼需要有一個專門的Runner來處理IO任務的原因。獲取諸如ui.Image這樣的資源只有透過async call,當這個呼叫發生的時候Flutter Framework告訴IO Runner進行剛剛提到的那些圖片非同步操作。這樣GPU Runner可以使用IO Runner準備好的圖片資料而不用進行額外的操作。
使用者操作,無論是Dart Code還是Native Plugins都是沒有辦法直接訪問IO Runner。儘管Embeder可以將一些一般複雜任務排程到IO Runner,這不會直接導致Flutter應用卡頓,但是可能會導致圖片和其它一些資源載入的延遲間接影響效能。所以建議為IO Runner建立一個專用的執行緒
android & iOS平臺上面每一個Engine例項啟動的時候會為UI,GPU,IO Runner各自建立一個新的執行緒。所有Engine例項共享同一個Platform Runner執行緒
isolate
An isolated Dart execution context
isolate是Dart對actor併發模式的實現。執行中的Dart程式由一個或多個actor組成,actor也就是Dart概念裡面的isolate。isolate是隔離的,每個isolate有自己的記憶體和單執行緒執行的實體. isolate之間不互相共享記憶體,且獨立GC。
isolate中的程式碼是順序執行的,且是單執行緒,所以不存在資源競爭和變數狀態同步的問題,也就不需要鎖。Dart中的併發都是多個isolate並行實現的
由於isolate不共享記憶體,所以isolate之間不能直接互相通訊,只能透過Port進行通訊,而且是非同步的
Flutter Engine Runners與Dart Isolate
Dart的Isolate是Dart虛擬機器自己管理的,Flutter Engine無法直接訪問。Root Isolate透過Dart的C++呼叫能力把UI渲染相關的任務提交到UI Runner執行, 這樣就可以跟Flutter Engine相關模組進行互動,Flutter UI相關的任務也被提交到UI Runner也可以相應的給Isolate一些事件通知,UI Runner同時也處理來自App方面Native Plugin的任務。 Dart isolate跟Flutter Runner是相互獨立的,它們透過任務排程機制相互協作。
Dart記憶體管理
Dart VM將記憶體管理分為新生代(New Generation)和老年代(Old Generation)
-
新生代:初次分配的物件都位於新生代中,該區域主要是存放記憶體較小並且生命週期較短的物件,比如區域性變數。新生代會頻繁執行記憶體回收(GC),回收採用“複製-清除”演算法,將記憶體分為兩塊,執行時每次只使用其中的一塊,另一塊備用。當發生GC時,將當前使用的記憶體塊中存活的物件複製到備用記憶體塊中,然後清除當前使用記憶體塊,最後,交換兩塊記憶體的角色。
-
老年代: 在新生代的GC中“倖存”下來的物件,它們會被轉移到老年代中。老年代存放生命力週期較長,記憶體較大的物件。老年代的GC回收採用“標記-清除”演算法,分成標記和清除兩個階段。在標記階段會觸發停頓,多執行緒併發的完成對垃圾物件的標記,降低標記階段耗時。在清理階段,由GC執行緒負責清理回收物件,和應用執行緒同時執行,不影響應用執行。
Flutter中的image所佔的記憶體
Android將中記憶體分java記憶體或native記憶體,通常在程式碼中的申請的記憶體都在這兩個範圍內
java記憶體是指java或kotlin分配的記憶體物件
native記憶體是指由C/C++中分配的記憶體,也包括一些android原生系統佔用的記憶體,如影像資源和其他圖形等
Flutter中的image佔用的不用這兩種記憶體,而是Graphics記憶體,Graphics記憶體記憶體是指圖形緩衝區佇列向螢幕顯示畫素所使用的記憶體,圖形緩衝區是指GL表面,GL紋理等。Graphics記憶體是與CPU共享的記憶體,而不是GPU專用的記憶體
Flutter執行模式
Flutter常見的種執行模式:Debug,Release和Profile
Release和Profile模式比較類似,不用之處在於Profile模式的服務擴充套件的支援,支援跟蹤,以及最小化使用跟蹤資訊需要的依賴。Profile並不支援模擬器,原因在於模擬器上的診斷並不代表真實的效能。所有重點截介紹
Debug和Release的差異
-
Debug模式:使用JIT編譯,支援模擬器和裝置。開啟了斷言支援,包括所有的除錯資訊,服務擴充套件和Observatory等除錯輔助。此模式為快速開發和執行做了最佳化,但並未對執行速度,包大小和部署做最佳化。
所以能實現秒級別的hot reload -
Release模式:使用AOT編譯,只支援真機,不支援模擬器。關閉了所有斷言,儘可能多地去掉了除錯資訊,關閉了所有除錯工具。為快速啟動,快速執行,包大小做了最佳化。禁止了所有除錯輔助手段,服務擴充套件。
Flutter Platform Channel
Platform Channel用來實現flutter和Native之間的通訊,實現方式類似遠端通訊。
Flutter定義了三種Channel:
- BasicMessageChannel:用於傳遞字串和半結構化的資訊
- MethodChannel:用於傳遞方法呼叫(method invocation)
- EventChannel: 用於資料流(event streams)的通訊
這三種channel的工作原理都一致,都用三個基本的屬性:
- name: String型別,代表Channel的名字,也是其唯一識別符號
- Messager:BinaryMessenger型別,代表訊息信使,是訊息的傳送與接收的工具
- codec: MessageCodec型別或MethodCodec型別,代表訊息的編解碼器
BinaryMessenger是Native端與Flutter端通訊的工具,其通訊使用的訊息格式為二進位制格式資料。初始化一個Channel,並向該Channel註冊處理訊息的Handler時,實際上會生成一個與之對應的BinaryMessageHandler,並以channel name為key,註冊到BinaryMessenger中。當Flutter端傳送訊息到BinaryMessenger時,BinaryMessenger會根據其入參channel找到對應的BinaryMessageHandler,並交由其處理。
BinaryMessenger只和BinaryMessageHandler通訊。而Channel和BinaryMessageHandler則是一一對應的。由於Channel從BinaryMessageHandler接收到的訊息是二進位制格式資料,無法直接使用,故Channel會將該二進位制訊息透過Codec(訊息編解碼器)解碼為能識別的訊息並傳遞給Handler進行處理。
當Handler處理完訊息之後,會透過回撥函式返回result,並將result透過編解碼器編碼為二進位制格式資料,透過BinaryMessenger傳送回Flutter端。
Codec:息編解碼器,主要用來將二進位制格式的資料轉化為Handler能夠識別的資料,Flutter定義了兩種Codec:MessageCodec和MethodCodec
MessageCodec用於二進位制格式資料與基礎資料之間的編碼和解碼。有多重實現如:BinaryCodec, StringCodec, JSONMessageCodec等
MethodCodec用於二進位制資料與方法呼叫(MethodCall)和返回結果之間的編解碼。MethodChannel和EventChannel所使用的編解碼器均為MethodCodec。
MethodCodec用於MethodCall物件的編解碼,一個MethodCall物件代表一次從Flutter端發起的方法呼叫。MethodCall有2個成員變數:String型別的method代表需要呼叫的方法名稱,通用型別(Android中為Object,iOS中為id)的arguments代表需要呼叫的方法入參。
由於處理的是方法呼叫,MethodCodec多了對呼叫結果的處理。當方法呼叫成功時,使用encodeSuccessEnvelope將result編碼為二進位制資料,而當方法呼叫失敗時,則使用encodeErrorEnvelope將error的code、message、detail編碼為二進位制資料。
MethodCodec有兩種實現:JSONMethodCodec和StandardMethodCodec
由於Platform Channel執行在flutter App的UI Task Runner, 對應的native實現執行在Platform Task Runner,而Platform Task Runner執行在主執行緒,所以在native實現是不能進行耗時的操作,且Platform Task Runner是非執行緒安全的,所以要保證回撥函式在主執行緒中執行
Platform Channel支援大資料傳遞,傳遞大記憶體資料塊時,使用BasicMessageChannel以及BinaryCodec。而整個資料傳遞的過程中,唯一可能出現資料複製的位置為native二進位制資料轉化為Dart語言二進位制資料。若二進位制資料大於閾值時(目前閾值為1000byte)則不會複製資料,直接轉化,否則複製一份再轉化。
學習
看到這裡,你瞭解了嗎?點個贊支援一下
想要學習的朋友可以詳細瞭解哦
Android學習PDF_原始碼筆記_面試文件_進階影片.pdf
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2665688/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 00-跨平臺開發之FlutterFlutter
- 跨平臺開發Flutter初體驗Flutter
- 身份證識別API有什麼作用?讓我們來了解一下API
- 通過面試題,讓我們來了解Collection面試題
- Flutter 記錄 - Flutter 與跨平臺框架們的眾樂樂Flutter框架
- 跨平臺開發時代的 (再次) 到來?
- flutter跨平臺開發之App升級方案FlutterAPP
- Flutter嚐鮮:跨平臺移動應用開發Flutter
- [Flutter翻譯]Flutter時代的多平臺VS跨平臺Flutter
- [譯] 使用 Flutter 實現跨平臺移動端開發Flutter
- 跨平臺技術演進及Flutter未來Flutter
- 跨平臺開發框架的大旗框架
- 遊戲開發全流程,我們請AI來回答一下遊戲開發AI
- Flutter 基礎(一)移動開發的跨平臺技術演進Flutter移動開發
- Dart Editor——跨平臺的開發工具Dart
- Flutter 實現原理及在馬蜂窩的跨平臺開發實踐Flutter
- Flutter實戰(三)檢驗Flutter的跨平臺能力Flutter
- 跨平臺開發框架 Lynx 初探框架
- QT6跨平臺開發QT
- 使用c++開發跨平臺的程式C++
- 使用wxdindows開發跨平臺的介面(轉)
- 想做大模型開發前,先來了解一下MoE大模型
- Flutter 到底能不能成為“跨平臺開發終極之選”?Flutter
- 熱火朝天的智慧經營系統到底是什麼?我們先來了解一下
- 過來人的增收絕招:跨平臺讓遊戲ARPU翻倍遊戲
- Flutter 學習路線圖!跨平臺開發必備,不可錯過的Flutter進階歷程!Flutter
- 我們打算聊聊雲平臺的那些事,嘉賓們都來了,你不來嗎?
- APP跨平臺開發技術分析APP
- 移動跨平臺開發深度解析
- 跨平臺開發技術簡介!
- hybird跨平臺移動app開發APP
- c++跨平臺開發經驗C++
- 移動端跨平臺開發的深度解析
- 跨平臺開發,各種巨集的定義
- 想了解一個異地多校平臺的架構演進過程嗎? 讓我來告訴你!架構
- Flutter - 不一樣的跨平臺解決方案Flutter
- 讓我們來聊聊前端的工程化前端
- [譯] 讓我們來簡化 UserDefaults 的使用