| 導語 所有的跨平臺方案,不管是最早的WebApp和HybridApp,還是之前非常火熱的RN和Weex,都面臨著如何平衡跨平臺性和效率這一問題。Flutter作為新一代的跨平臺解決方案,傳說中效能直逼原生,它為何如此優秀呢?讓我們以Flutter的通訊機制為起點,一起探索Flutter和原生之間的小祕密。注:由於作者不太熟悉iOS開發,所以本文大部分使用的都是Android視角。
一、跨平臺機制大不同
跨平臺解決方案由來已久,《聊聊移動端跨平臺開發的各種技術》的作者將其大致分為了以下幾種流派:
-
Web 流:也被稱為 Hybrid 技術,它基於 Web 相關技術來實現介面及功能
-
程式碼轉換流:將某個語言轉成 Objective-C、Java 或 C#,然後使用不同平臺下的官方工具來開發
-
編譯流:將某個語言編譯為二進位制檔案,生成動態庫或打包成 apk/ipa/xap 檔案
-
虛擬機器流:通過將某個語言的虛擬機器移植到不同平臺上來執行
以往最早的Hybrid開發,主要依賴於WebView。但是WebView是一個很重的控制元件,很容易產生記憶體問題,而且複雜的UI在WebView上顯示的效能不好。react-native技術拋開了WebView,利用JavaScriptCore來做橋接,將js呼叫轉為native呼叫,只犧牲了小部分效能獲取的跨平臺開發。
Flutter實現跨平臺採用了更為徹底的方案。它既沒有采用WebView也沒有采用JavaScriptCore,而是自己實現了一臺UI框架,然後直接系統更底層渲染系統上畫UI。所以它採用的開發語言不是JS,而是Dart。Dart語言有著適合Flutter的良好的特性,詳情可以看FAQ.為什麼Flutter選擇使用Dart語言?
二、Flutter和native的通訊方式
Flutter和native間的通訊,應該分為 Flutter主動傳送 和 native主動傳送 兩種情況,而Flutter的官方文件卻僅僅介紹了Flutter主動傳送的方式。對於 native主動傳送的方式提供了一個plugin作為例子。具體的使用方式可以點開官網連結檢視,在這就不再贅述了,主要是探尋一下兩邊的程式碼到底是怎麼相互呼叫的。
- MethodChannel:
Flutter之間的訊息傳遞,是通過 MethodChannel 來完成。我們先看它的建構函式。可以發現,需要一個 name和一個可選的 MethodCodec。 name是這個 MethodChannel的標識,在後面會用到。 MethodCodec是個編/解碼器,其決定了我們能傳遞什麼型別的資料。 StandarMethodCodec有如下規則:
Dart名(rtx) | Android | iOS |
---|---|---|
null | null | nil (NSNull when nested) |
bool | java.lang.Boolean | NSNumber numberWithBool: |
int | java.lang.Integer | NSNumber |
int | if 32 bits not enough | java.lang.Long |
int | if 64 bits not enough | java.math.BigInteger |
double | java.lang.Double | NSNumber numberWithDouble: |
String | java.lang.String | NSString |
Uint8List | byte[] | FlutterStandardTypedData |
Int32List | int[] | FlutterStandardTypedData |
Int64List | long[] | FlutterStandardTypedData |
Float64List | double[] | FlutterStandardTypedData |
List | java.util.ArrayList | NSArray |
Map | java.util.HashMap | NSDictionary |
- invokeMethod()
檢視invokeMethod()方法, method為 MethodCall的標識, arguments則自然是引數了,值得注意的是這裡的引數必須要遵守上面的規則(預設情況下不能直接傳自定義類,但是我們可以將其轉為Json之後再傳遞,也可以去改造 MethodCode,以此進行復雜資料結構的傳遞)。 注:可以看到,其是一個 async標記的方法,返回值為Future。那麼我們在接受這個方法的返回值的時候,就必須要使用 await進行修飾。要呼叫使用 await,必須在有 async標記的函式中執行。具體呼叫和使用的方式可以看官網的例子。在這我們先繼續深入。
- BinaryMessages.send()
檢視send()方法可以發現一個很有意思的事情,如果 _mockHandlers取出來的值不為空的話,則message會直接被對應handler處理,不會再傳送。檢視 setMockMessageHandler()的註釋,官方的解釋是,該方法會將Message攔截,可以用於測試。
- _sendPlatformMessage()——window.sendPlatformMessage()
這裡呼叫了一個native方法,那麼我們就需要去找這個native方法。
- native 遇到native方法,先去engine中找同名的檔案試試看,果然在window.cc中,找到了這個方法。然後可以找到對應的native方法。
這段程式碼比較長,我們一點點來看。 首先看64行,可以獲取到一個有用資訊,這個套機制僅能在主執行緒(isolate)上執行。 然後看最關鍵的方法: dart_state->window()->client()->HandlePlatformMessage()
順著找下去,可以在 WindowClient中找到 HandlePlatformMessage(),但是可以發現,這是一個純虛擬函式。這裡使用了一個比較取巧的方式去找,直接去找都有哪個類繼承了 WindowClient,可以發現只有 RuntimeController是繼承了這個類的,去看看這個方法的實現。
那麼這個client是什麼呢?是一個 RuntimeDelegate …………………… 跳啊跳啊,終於在 PlatformViewAndroid 找到了實現(當然對應的iOS的也有一個,這裡因為我比較熟悉Android,就只把Android的拿出來看),中間的過程沒什麼營養就不進行詳述了。
OK,經歷了重重關卡,終於找到了jni的方法。那麼也就是說,只要找到 g_handle_platform_message_method 所指向的方法,就能走入Android的世界了。(可以提前關注一下 pending_responses_,這個變數在後面會提到)
用簡單暴力點的方法,直接去找賦值語句,發現只有一處,那麼不用想,肯定是它了。
然後再找這個類名,就可以在Flutter的Android工程中找到這個方法了。
- Android
終於回到了JAVA的世界,還是比較感動的。其實這段程式碼核心就在 onMessage()直接看一看這個方法的實現。
果然是一個抽象函式,而且有三個實現。那麼我們就不能亂猜了,再回頭看一下 handler這變數到底是個什麼型別。從133行可以看到 handler 是以channel的name為key從一個map( mMessageHandlers)中取出來的,那這個值是在哪被設定的呢?線索彷彿到這就斷掉了。別急,我們先來回憶一下,使用 MethodChannel的步驟。(下圖為中文官網教程截圖)
好了重點我已經圈出來了,跳進去看看就明白了。
那就確定了 handler是 IncomingMethodCallHandler的物件。直接去看它的 onMessage方法。
終於,我們看到了熟悉的 onMethodCall方法。
- 訊息回覆 方法是呼叫了,那下一步就該考慮資料怎麼傳回去了。還記得第6點一開始有個回撥函式嗎?上面的幾個reply呼叫的就是這個回撥函式。
再看看這個回撥函式,分別是呼叫了兩個native層的函式。empty的那個就不用看了,知道非empty的怎麼傳自然也就知道它怎麼傳了。在 platform_view_android_jni.cc中,我們可以找到對應的函式。
message_response看起來就像是一個回撥函式。它是從 pending_responses_中取出來的。還記得第4點中有一個回撥函式嗎?它在 native層中被封裝,並置入了 pending_responses_中(具體可以仔細看第5點的過程,中間有提到,在這就不重複貼圖了),那麼這裡的 message_response經過一些處理之後,最終還是會回到那個回撥函式,這裡的過程就不詳述了。
- 總結 好了,至此一個閉環也就形成了。可以看到,整體的通訊流程是一個V形。
《IVWEB 技術週刊》 震撼上線了,關注公眾號:IVWEB社群,每週定時推送優質文章。