Flutter 與 Native 通信詳解(上):原理探究

Flutter 簡(jiǎn)介

Flutter 是 Google 開發(fā)的一套全新的跨平臺(tái)框架,不同于 React Native 封裝原生應(yīng)用層接口,然后通過(guò) JavaScriptCore 轉(zhuǎn)義 JavaScript 來(lái)生成原生界面的方案,F(xiàn)lutter 拋開原生控件,用 Dart 語(yǔ)言重寫了一套跨平臺(tái)的UI組件(widget),渲染引擎依靠跨平臺(tái)的 Skia 圖形庫(kù)來(lái)實(shí)現(xiàn),依賴系統(tǒng)的只有圖形繪制相關(guān)的接口,可以在最大程度上保證不同平臺(tái)、不同設(shè)備的體驗(yàn)一致性,前段時(shí)間 Google I/O 2019 大會(huì)上宣布 Flutter 還將支持 Web端、桌面端和嵌入式設(shè)備,可謂真正實(shí)現(xiàn)了 Write once, run anywhere.

Flutter 和 Native 通信的必要性

看起來(lái) Flutter 做了很多,似乎完全不需要使用原生平臺(tái)的功能也能構(gòu)建一個(gè) App,但是大部分情況下,光靠 Flutter 是不夠的,比如當(dāng)你的App需要如下功能時(shí):

  • 獲取設(shè)備信息,像電池電量、網(wǎng)絡(luò)連接狀態(tài)等
  • 使用相機(jī),麥克風(fēng),藍(lán)牙,定位等功能
  • 數(shù)據(jù)持久化,通知,App 生命周期等

當(dāng)然,這些平臺(tái)(即Native端,下同)相關(guān)的功能 Flutter 其實(shí)也可以封裝在 Flutter 框架中,讓開發(fā)者不用關(guān)心平臺(tái),完全通過(guò)Flutter 構(gòu)建App,多好啊;但是這樣做會(huì)帶來(lái)一些問(wèn)題,且不論將Android、iOS(之后還會(huì)包括Web端、桌面端、嵌入式設(shè)備)各端的平臺(tái)功能都封裝好的難易度,就說(shuō)如果都封裝到 Flutter 中了,那么Flutter framework 變得比現(xiàn)在大很多,且只要平臺(tái)相關(guān)API有所更新,F(xiàn)lutter 也得跟著修改,會(huì)出現(xiàn) Flutter 一直在追逐平臺(tái)版本的情況,也容易出現(xiàn)兼容性問(wèn)題和版本碎片化.

所以,F(xiàn)lutter 團(tuán)隊(duì)并沒有選擇封裝平臺(tái)相關(guān)的API,選擇了另一種更靈活的做法:Flutter 依舊用 Dart 及跨平臺(tái)的渲染引擎來(lái)實(shí)現(xiàn) Write once, run anywhere的界面和業(yè)務(wù)邏輯,在涉及到平臺(tái)相關(guān)的功能時(shí),則還是由開發(fā)者在平臺(tái)端實(shí)現(xiàn),然后提供了一個(gè)叫做 Platform Channel 的機(jī)制來(lái)進(jìn)行 Flutter 和平臺(tái)端之間的通信,這樣做可以將Flutter和平臺(tái)的耦合度降到最低。

image

Platform Channel 見名知意,即平臺(tái)通道,是Flutter和原生平臺(tái)通信的通道,分為以下三類:

  • Message channel:用于傳遞字符串和半結(jié)構(gòu)化的信息。
  • Method channel:用于傳遞方法調(diào)用(method invocation)。
  • Event channel:用于數(shù)據(jù)流(event streams)的通信。

本篇文章先不著急介紹這三類 Platform channel,先來(lái)看看 Flutter 和平臺(tái)端通信的原理。

Flutter 和 Native 通信的原理

消息信使:BinaryMessenger

從底層來(lái)看,F(xiàn)lutter和平臺(tái)端通信的方式是發(fā)送異步的二進(jìn)制消息,該基礎(chǔ)通信方式在Flutter端由BinaryMessages來(lái)實(shí)現(xiàn),? 而在Android端是一個(gè)接口BinaryMessenger,其具體實(shí)現(xiàn)為FlutterNativeView,在iOS端是一個(gè)協(xié)議 FlutterBinaryMessenger,FlutterViewController遵守并實(shí)現(xiàn)了這個(gè)協(xié)議。

image

其主要實(shí)現(xiàn)了發(fā)送二進(jìn)制消息和設(shè)置消息處理回調(diào)的方法,如Flutter端BinaryMessages 的部分源碼:

    // 發(fā)送二進(jìn)制消息
  static Future<ByteData> send(String channel, ByteData message) {
    final _MessageHandler handler = _mockHandlers[channel];
    if (handler != null)
      return handler(message);
    return _sendPlatformMessage(channel, message);
  }

  // 注冊(cè)消息處理回調(diào)
  static void setMessageHandler(String channel, Future<ByteData> handler(ByteData message)) {
    if (handler == null)
      _handlers.remove(channel);
    else
      _handlers[channel] = handler;
  }

消息通道:Channel

同時(shí),為了區(qū)分不同用途的消息,每個(gè)消息都可以為其指定一個(gè)channel,即以上消息收發(fā)方法中的參數(shù) channel,channel 僅僅是一個(gè)字符串,下面的例子使用foo當(dāng)做消息收發(fā)的channel:

//向平臺(tái)發(fā)送二進(jìn)制消息.
final WriteBuffer buffer = WriteBuffer()
  ..putFloat64(3.1415)
  ..putInt32(12345678);
final ByteData message = buffer.done();
await BinaryMessages.send('foo', message);
print('Message sent, reply ignored');

Android 端(Kotlin):

// 接受并解析來(lái)自Flutter端的消息
flutterView.setMessageHandler("foo") { message, reply ->
  message.order(ByteOrder.nativeOrder())
  val x = message.double
  val n = message.int
  Log.i("MSG", "Received: $x and $n")
  reply.reply(null)
}

iOS端(OC):

// 接受并解析來(lái)自Flutter端的消息
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
[controller setMessageHandlerOnChannel:@"foo" binaryMessageHandler:^(NSData * _Nullable message, FlutterBinaryReply  _Nonnull reply) {
    Float64 x;
    NSData *data8 = [message subdataWithRange:NSMakeRange(0, 8)];
    [data8 getBytes:&x length:sizeof(x)];
    
    int32_t n;
    NSData *data4 = [message subdataWithRange:NSMakeRange(8, 4)];
    [data4 getBytes:&n length:sizeof(n)];
    
    NSLog(@"Received %f and %d", x, n);
    reply(nil);
}];

消息通信是雙向的,所以我們也可以從平臺(tái)端向Flutter端發(fā)送消息,如下:

// 從 Android 端發(fā)送一個(gè)二進(jìn)制消息
val message = ByteBuffer.allocateDirect(12)
message.putDouble(3.1415)
message.putInt(123456789)
flutterView.send("foo", message) { _ ->
  Log.i("MSG", "Message sent, reply ignored")
}
// 從 iOS端發(fā)送一個(gè)二進(jìn)制消息
NSMutableData *message = [NSMutableData dataWithCapacity:12];
Float64 x = 3.1415;
int32_t n = 12345678;
[message appendBytes:&x length:sizeof(x)];
[message appendBytes:&n length:sizeof(n)];
[controller sendOnChannel:@"foo" message:message binaryReply:^(NSData * _Nullable reply) {
    NSLog(@"Message sent, reply ignored");
}];
// Flutter端接收
BinaryMessages.setMessageHandler('foo', (ByteData message) async {
  final ReadBuffer readBuffer = ReadBuffer(message);
  final double x = readBuffer.getFloat64();
  final int n = readBuffer.getInt32();
  print('Received $x and $n');
  return null;
});

消息處理器:MessageHandler

通過(guò)上面的示例代碼可以發(fā)現(xiàn),消息是通過(guò)提前設(shè)置的MessageHandler來(lái)處理的,所有的 MessageHandler 都被保存在一個(gè) HashMap 中,key 即為其對(duì)應(yīng)的 channel 字符串,因此每個(gè)channel最多只能有一個(gè) MessageHandler,后設(shè)置的會(huì)將之前的覆蓋掉,取消一個(gè) MessageHandler 的方式也就是設(shè)置對(duì)應(yīng) channel 的 MessageHandler 為 null.

在 MessageHandler 中最后的消息回復(fù)動(dòng)作是必須的,每個(gè)消息的發(fā)送都應(yīng)該對(duì)應(yīng)一個(gè)異步的消息回復(fù),即使沒有返回值,也需要回復(fù) null,就像示例代碼中一樣,這是為了使得Dart中的Future和平臺(tái)端的回調(diào)函數(shù)得以完成和執(zhí)行。

還有一點(diǎn)需要注意的是,在平臺(tái)端消息的發(fā)送和回復(fù)都必須在主線程進(jìn)行(即UI線程),而在flutter端,每個(gè) Dart isolate 只有一個(gè)線程,所以flutter端不用擔(dān)心用錯(cuò)線程所導(dǎo)致的問(wèn)題。

關(guān)于Dart isolate詳細(xì)講解可以參考閑魚團(tuán)體的文章:Flutter Engine線程管理與Dart Isolate機(jī)制

消息編解碼器:Codec

在上面的例子中,我們其實(shí)已經(jīng)能夠在Flutter和平臺(tái)間進(jìn)行相互通信了,但是收發(fā)的數(shù)據(jù)都是二進(jìn)制的,這就需要開發(fā)者考慮更多的細(xì)節(jié),如字節(jié)順序(大小端)和怎么表示更高級(jí)的消息類型,如字符串,map等,因此,F(xiàn)lutter 還提供了消息編解碼器(Codec), 用于高級(jí)數(shù)據(jù)類型(字符串,map等)和二進(jìn)制數(shù)據(jù)(byte)之間的轉(zhuǎn)換,即消息的序列化和反序列化。

有了消息編解碼器,我們?cè)诰幊虝r(shí)就不用直接對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行操作了,極大的降低了編程復(fù)雜度,F(xiàn)lutter 定義了四種基本的消息編解碼器類型:

  • BinaryCodec:BinaryCodec是最為簡(jiǎn)單的一種Codec,因?yàn)槠浞祷刂殿愋秃腿雲(yún)⒌念愋拖嗤?,均為二進(jìn)制格式(Android中為ByteBuffer,iOS中為NSData)。實(shí)際上,BinaryCodec在編解碼過(guò)程中什么都沒做,只是原封不動(dòng)將二進(jìn)制數(shù)據(jù)消息返回而已。或許你會(huì)因此覺得BinaryCodec沒有意義,但是在某些情況下它非常有用,比如使用BinaryCodec可以使傳遞內(nèi)存數(shù)據(jù)塊時(shí)在編解碼階段免于內(nèi)存拷貝。

  • StringCodec:使用 UTF-8 編碼格式對(duì)字符串?dāng)?shù)據(jù)進(jìn)行編解碼,在Android平臺(tái)轉(zhuǎn)換為 java.util.String 類型,iOS 平臺(tái)則對(duì)應(yīng)著 NSString.

  • JSONMessageCodec:JSONMessageCodec用于處理 JSON 數(shù)據(jù)類型(字符串型,數(shù)字型,布爾型,null,只包含這些類型的數(shù)組,和key為string類型,value為這些類型的map),在編碼過(guò)程中,數(shù)據(jù)會(huì)被轉(zhuǎn)換為JSON字符串,然后在使用 UTF-8 格式轉(zhuǎn)換為字節(jié)型。其在iOS端使用了NSJSONSerialization作為序列化的工具,而在Android端則使用了其自定義的JSONUtil與StringCodec作為序列化工具。

  • StandardMessageCodec:StandardMessageCodec 可以認(rèn)為是 JSONMessageCodec 的升級(jí)版,能夠處理的數(shù)據(jù)類型要比 JSONMessageCodec 更普遍一些,且在處理 int 型數(shù)據(jù)時(shí),會(huì)根據(jù) int 數(shù)據(jù)的大小來(lái)轉(zhuǎn)為平臺(tái)端的32位類型(int)或者是64位類型(long),StandardMessageCodec 也是 Flutter Platform channel 的默認(rèn)編解碼器,下圖列出了 StandardMessageCodec 能處理的數(shù)據(jù)類型和在各平臺(tái)對(duì)應(yīng)的類型:

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
int, if 32 bits not enough java.lang.Long NSNumber numberWithLong:
int, if 64 bits not enough java.math.BigInteger FlutterStandardBigInteger (已廢棄)
double java.lang.Double NSNumber numberWithDouble:
String java.lang.String NSString
Uint8List byte[] FlutterStandardTypedData typedDataWithBytes:
Int32List int[] FlutterStandardTypedData typedDataWithInt32:
Int64List long[] FlutterStandardTypedData typedDataWithInt64:
Float64List double[] FlutterStandardTypedData typedDataWithFloat64:
List java.util.ArrayList NSArray
Map java.util.HashMap NSDictionary

需要注意的是 BigInteger 類型在 Dart 2.0 已被廢棄,這是因?yàn)樵贒art 1.0 時(shí),int 類型沒有固定的大小限制,32位、64位數(shù)字都可以用 int 表示,而當(dāng)Dart中的int型傳到平臺(tái)端時(shí),就會(huì)根據(jù)其具體大小轉(zhuǎn)為 int型、long型或者更大的類型,BigInteger 就是用來(lái)標(biāo)識(shí)比64位int更大的類型的;但是到了Dart 2.0 ,int 類型大小固定為了 64位,如果想要傳遞更大的數(shù)字,則需要轉(zhuǎn)換為字符串類型。

再深入一點(diǎn),通過(guò)查看 flutter engine源碼 可以發(fā)現(xiàn),當(dāng)message或response需要被編碼為二進(jìn)制數(shù)據(jù)時(shí),會(huì)調(diào)用StandardMessageCodec 的 writeValue方法,該方法接收一個(gè)名為value的參數(shù),并根據(jù)其類型,向二進(jìn)制數(shù)據(jù)容器(NSMutableData或ByteArrayOutputStream)寫入該類型對(duì)應(yīng)的type值,再將該數(shù)據(jù)轉(zhuǎn)化為二進(jìn)制表示,并寫入二進(jìn)制數(shù)據(jù)容器。

?而message或者response需要被解碼時(shí),使用的是StandardMessageCodec的readValue方法,該方法接收到二進(jìn)制格式數(shù)據(jù)后,會(huì)先讀取一個(gè)byte表示其type,再根據(jù)其type將二進(jìn)制數(shù)據(jù)轉(zhuǎn)化為對(duì)應(yīng)的數(shù)據(jù)類型。

假設(shè)我們要發(fā)送的消息為int型的數(shù)字 100,當(dāng)這個(gè)值被轉(zhuǎn)化為二進(jìn)制數(shù)據(jù)時(shí),會(huì)先向二進(jìn)制數(shù)據(jù)容器寫入int類型對(duì)應(yīng)的type值:3,再寫入由100轉(zhuǎn)化而得的4個(gè)byte。而當(dāng)Flutter端接收到該二進(jìn)制數(shù)據(jù)時(shí),先讀取第一個(gè)byte值,并根據(jù)其值得出該數(shù)據(jù)為int類型,接著讀取緊跟其后的4個(gè)byte,并將其轉(zhuǎn)化為dart類型的int,反之亦然。


image

對(duì)于字符串、列表、字典的編碼會(huì)稍微復(fù)雜一些。字符串使用UTF-8編碼得到的二進(jìn)制數(shù)據(jù)是長(zhǎng)度不定的,因此會(huì)在寫入type后,先寫入一個(gè)代表二進(jìn)制數(shù)據(jù)長(zhǎng)度的size,再寫入數(shù)據(jù)。列表和字典則是寫入type后,先寫入一個(gè)代表列表或字典中元素個(gè)數(shù)的size,再遞歸調(diào)用writeValue方法將其元素依次寫入。

至于消息編解碼器的具體用法,就要說(shuō)到 Platform channel 了,其實(shí)就是對(duì)于以上介紹的幾個(gè)通信基礎(chǔ)要素的組合封裝,這里由于篇幅問(wèn)題,下篇文章再說(shuō)。

總結(jié)

到這里,想必你已經(jīng)理解了flutter和平臺(tái)端通信的原理:通過(guò)消息信使(BinaryMessenger)來(lái)異步的收發(fā)二進(jìn)制消息,每個(gè)消息都有對(duì)應(yīng)的消息渠道(channel)來(lái)區(qū)分不同的消息用途,然后使用不同的消息編解碼器(Codec)對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行序列化與反序列化,最后通過(guò)注冊(cè)的消息處理器(MessageHandler)來(lái)處理并回復(fù)對(duì)應(yīng)的消息。


image

參考

Flutter Platform Channels
深入理解Flutter Platform Channel
flutter engine 源碼
Writing custom platform-specific code

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文為 Flutter 與 Native 通信詳解系列文章的下篇,在閱讀本篇文章前,建議先看一下上篇文章:Flut...
    等開會(huì)閱讀 3,025評(píng)論 0 5
  • Flutter 與 Native 通信原理 Flutter 是一個(gè)跨平臺(tái)開發(fā)框架,它使用了一種全新的方式,自己重寫...
    瀟瀟瀟瀟瀟瀟瀟閱讀 5,786評(píng)論 1 18
  • 通信在任何程序中都是舉足輕重的,在做Flutter開發(fā)的時(shí)候通常離不了Flutter和Native之間的通信,比如...
    aimaile閱讀 4,007評(píng)論 4 2
  • “慎獨(dú)”就是當(dāng)你獨(dú)自一個(gè)人的時(shí)候,你是不是還能保持好人的樣子。 吳伯凡老師曾經(jīng)講過(guò)“慎獨(dú)”,他說(shuō),慎獨(dú)最生動(dòng)的例子...
    安寧姑娘閱讀 1,100評(píng)論 0 0
  • 你的控制欲又在作祟嗎?企圖想讓他人按照你的想法來(lái)過(guò)日子?你總覺得你孤單,你總是覺得你是家里老大就應(yīng)該管?就應(yīng)該出面...
    蝸小簡(jiǎn)閱讀 189評(píng)論 0 0

友情鏈接更多精彩內(nèi)容