跨平臺開發的救星-讓咱們來了解一下flutter

第一次看文章的朋友能夠關注個人專欄,會不按期發佈Android面試內容、進階專題等等。javascript

簡介

不少人已經用上了flutter,今天就來介紹一下java

Flutter 架構

image

imageandroid

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。引擎啓動的時候爲其增長了必要的綁定,使其具有調度提交渲染幀的能力。

  1. 對於每一幀,引擎要作的事情有:

  2. Root isolate通知Flutter Engine有幀須要渲染。

  3. Flutter Engine通知平臺,須要在下一個vsync的時候獲得通知。

  4. 平臺等待下一個vsync

  5. 對建立的對象和Widgets進行Layout並生成一個Layer Tree,Layer Tree立刻被提交給Flutter Engine。當前階段沒有進行任何光柵化,這個步驟僅是生成了對須要繪製內容的描述。

  6. 建立或者更新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

image

image

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)則不會拷貝數據,直接轉化,不然拷貝一份再轉化。

學習

看到這裏,你瞭解了嗎?點個贊支持一下

相關文章
相關標籤/搜索