Flutter是Google使用Dart語言開發的一套移動應用開發框架。它不同于其他開發框架:
因為Flutter使用AOT預編譯代碼為機器碼,所以它的運行效率更高。
Flutter的UI控件并沒有使用底層的原生控件,而是使用Skia渲染引擎繪制而成,因為不依賴底層控件,所以多端一致性非常好。
Flutter的擴展性也非常強,開發者可以通過Plugin與Native進行通信。
參考深入理解Flutter Platform Channel
Platform Channel 主要用于Flutter和原生端的數據傳遞。
Flutter定義了三種不同類型的Channel,分別是
- BasicMessageChannel:用于傳遞字符串和半結構化的數據;
- MethodChannel:用于傳遞方法調用;
- EventChannel:用于數據流的通信;
消息使用平臺通道在客戶端(UI)和宿主(平臺)之間傳遞,如下圖所示:
根據架構圖,我們可以看出在Flutter端,MethodChannel 允許發送與方法調用相對應的消息。Android上的MethodChannel 啟用接收方法調用并發回結果給Flutter端。而這種數據傳遞方式還可以反向調用。為了保證用戶界面保持相應而不卡頓,消息和響應以異步的形式進行傳遞。
以確保用戶界面能夠保持響應。
Flutter 是通過 Dart 異步發送消息的。即便如此,當你調用一個平臺方法時,也需要在主線程上做調用。
具體使用方法請參考Demo實現了一個從Flutter端發起的方法調用,從原生端獲取數據并返回給Flutter端用于展示。以下是基本使用方法。
//flutter 代碼
class MethodChannelManager {
MethodChannel _methodChannel;
static MethodChannelManager _instance;
String _methodChannelName = "flutter_method_channel";
MethodChannelManager._internal() {
if (_methodChannel == null) {
_methodChannel = new MethodChannel(_methodChannelName);
}
}
static MethodChannelManager getInstance() {
if (_instance == null) {
_instance = new MethodChannelManager._internal();
}
return _instance;
}
Future<String> sendMessage() async {
Map<String, String> map = {"flutter": "這是一條來自flutter的參數"};
String message = await _methodChannel.invokeMethod("success", map);
return message;
}
Future<double> getRefreshRate() async {
return MethodChannel('fps_plugin').invokeMethod("getRefreshRate");
}
}
//android 原生端代碼
public class NativeBasePlugin implements MethodCallHandler, StreamHandler, FlutterPlugin , ActivityAware {
private Context applicationContext;
private MethodChannel methodChannel;
private EventChannel eventChannel;
public static String methodChannelName = "flutter_method_channel";
public static String eventChannelName = "flutter_event_channel";
public int index = 0;
/**
* Plugin registration.
*/
public static void registerWith(FlutterEngine flutterEngine) {
final NativeBasePlugin instance = new NativeBasePlugin();
instance.onAttachedToEngine(new ShimPluginRegistry(flutterEngine).registrarFor(methodChannelName).context(),
flutterEngine.getDartExecutor());
}
@Override
public void onAttachedToEngine(FlutterPluginBinding binding) {
onAttachedToEngine(binding.getApplicationContext(), binding.getBinaryMessenger());
}
private void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger) {
this.applicationContext = applicationContext;
methodChannel = new MethodChannel(messenger, methodChannelName);
methodChannel.setMethodCallHandler(this);
eventChannel = new EventChannel(messenger, eventChannelName);
eventChannel.setStreamHandler(this);
}
@Override
public void onMethodCall(MethodCall methodCall, Result result) {
switch (methodCall.method) {
case "success":
//解析參數
String value = methodCall.argument("flutter");
// Toast.makeText(applicationContext, "" + value, Toast.LENGTH_SHORT).show();
result.success("A");
break;
default:
result.notImplemented();
}
}
@Override
public void onDetachedFromEngine(FlutterPluginBinding binding) {
applicationContext = null;
methodChannel.setMethodCallHandler(null);
methodChannel = null;
eventChannel.setStreamHandler(null);
eventChannel = null;
}
@Override
public void onListen(Object arguments, EventSink events) {
/**
* CountDownTimer timer = new CountDownTimer(3000, 1000)中,
* 第一個參數表示總時間,第二個參數表示間隔時間。
* 意思就是每隔一秒會回調一次方法onTick,然后1秒之后會回調onFinish方法。
*/
CountDownTimer timer = new CountDownTimer(20000, 1000) {
public void onTick(long millisUntilFinished) {
events.success("index=" + millisUntilFinished / 1000 + "秒");
}
public void onFinish() {
events.success("時間結束了");
}
};
timer.start();
}
@Override
public void onCancel(Object arguments) {
//EventChannel 取消
index = 0;
}
}
Dart層方法調用的消息傳遞分析
首先,dart中會先創建一個MethodChannel對象,其名稱為flutter_method_channel”,這個名字很關鍵,必須與原生端的名字相對應,具體原因后邊會有解釋。通過異步方式調用invokeMethod方法傳入方法名來獲取信息 await _methodChannel.invokeMethod('success');
invokeMethod方法具體實現如下
@optionalTypeArgs
Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) async {
assert(method != null);
final ByteData result = await binaryMessenger.send(
name,
codec.encodeMethodCall(MethodCall(method, arguments)),
);
if (result == null) {
throw MissingPluginException('No implementation found for method $method on channel $name');
}
final T typedResult = codec.decodeEnvelope(result);
return typedResult;
}
通過BinaryMessages.send()方法來發送方法調用消息,我們可以看到send方法有兩個參數,第一個是channel的名稱,第二個是ByteData對象(使用codec對根據方法名和參數構建的MethodCall對象進行編碼得到的對象);codec對象是在MethodChannel對象創建時默認創建的StandardMethodCodec對象,其對MethodCall對象的編碼過程如下
const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger binaryMessenger ])
: assert(name != null),
assert(codec != null),
_binaryMessenger = binaryMessenger;
@override
ByteData encodeMethodCall(MethodCall call) {
final WriteBuffer buffer = WriteBuffer();
messageCodec.writeValue(buffer, call.method);
messageCodec.writeValue(buffer, call.arguments);
return buffer.done();
}
通過messageCodec將調用的方法名和傳遞的參數寫入到buffer中,messageCodec是一個StandardMessageCodec對象,在StandardMethodCodec對象創建時默認創建,其writeValue方法的實現如下
void writeValue(WriteBuffer buffer, dynamic value) {
if (value == null) {
buffer.putUint8(_valueNull);
} else if (value is bool) {
buffer.putUint8(value ? _valueTrue : _valueFalse);
} else if (value is double) { // Double precedes int because in JS everything is a double.
// Therefore in JS, both `is int` and `is double` always
// return `true`. If we check int first, we'll end up treating
// all numbers as ints and attempt the int32/int64 conversion,
// which is wrong. This precedence rule is irrelevant when
// decoding because we use tags to detect the type of value.
buffer.putUint8(_valueFloat64);
buffer.putFloat64(value);
} else if (value is int) {
if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) {
buffer.putUint8(_valueInt32);
buffer.putInt32(value);
} else {
buffer.putUint8(_valueInt64);
buffer.putInt64(value);
}
} else if (value is String) {
buffer.putUint8(_valueString);
final List<int> bytes = utf8.encoder.convert(value);
writeSize(buffer, bytes.length);
buffer.putUint8List(bytes);
} else if (value is Uint8List) {
buffer.putUint8(_valueUint8List);
writeSize(buffer, value.length);
buffer.putUint8List(value);
} else if (value is Int32List) {
buffer.putUint8(_valueInt32List);
writeSize(buffer, value.length);
buffer.putInt32List(value);
} else if (value is Int64List) {
buffer.putUint8(_valueInt64List);
writeSize(buffer, value.length);
buffer.putInt64List(value);
} else if (value is Float64List) {
buffer.putUint8(_valueFloat64List);
writeSize(buffer, value.length);
buffer.putFloat64List(value);
} else if (value is List) {
buffer.putUint8(_valueList);
writeSize(buffer, value.length);
for (final dynamic item in value) {
writeValue(buffer, item);
}
} else if (value is Map) {
buffer.putUint8(_valueMap);
writeSize(buffer, value.length);
value.forEach((dynamic key, dynamic value) {
writeValue(buffer, key);
writeValue(buffer, value);
});
} else {
throw ArgumentError.value(value);
}
}
上述代碼看出,Flutter與平臺端的消息傳遞支持12種類型,這12種類型分別與安卓和iOS中的類型相對應,看下面表格更直觀
writeValue方法其實就是將方法名和參數轉化為對應的二進制數據寫入buffer中,方法名都是String類型,我們就以String類型方法寫入過程來進行簡單說明,如果判斷一個value為String后,
1、調用buffer.putUint8(_valueString);先寫入對應的類型值,_valueString = 7;,所以將00000111二進制數據寫入buffer;
2、緊接著將value通過utf8編碼為int數組,然后將數組的長度數據通過writeSize(buffer, bytes.length);寫入buffer;
3、最后再將數組數據寫入buffer,至此一個方法名編碼完成;
其他類型的數據依次類推進行編碼,編碼完成后,將StandardMessageCodec對象編碼的ByteData數據通過BinaryMessages.send()方法發送出去,看下send方法的具體實現
@override
Future<ByteData> send(String channel, ByteData message) {
final MessageHandler handler = _mockHandlers[channel];
if (handler != null)
return handler(message);
return _sendPlatformMessage(channel, message);
}
會從_mockHandlers中查找是否緩存的有_MessageHandler對象,如果沒有則通過_sendPlatformMessage方法發送消息,
消息處理器:Handler
當我們接收二進制格式消息并使用Codec將其解碼為Handler能處理的消息后,就該Handler上場了。Flutter定義了三種類型的Handler,與Channel類型一一對應。我們向Channel注冊一個Handler時,實際上就是向BinaryMessager注冊一個與之對應的BinaryMessageHandler。當消息派分到BinaryMessageHandler后,Channel會通過Codec將消息解碼,并傳遞給Handler處理。
- MessageHandler
MessageHandler用戶處理字符串或者半結構化的消息,其onMessage方法接收一個T類型的消息,并異步返回一個相同類型result。MessageHandler的功能比較基礎,使用場景較少,但是其配合BinaryCodec使用時,能夠方便傳遞二進制數據消息。
- MethodHandler
MethodHandler用于處理方法的調用,其onMessage方法接收一個MethodCall類型消息,并根據MethodCall的成員變量method去調用對應的API,當處理完成后,根據方法調用成功或失敗,返回對應的結果。
- StreamHandler
StreamHandler與前兩者稍顯不同,用于事件流的通信,最為常見的用途就是Platform端向Flutter端發送事件消息。當我們實現一個StreamHandler時,需要實現其onListen和onCancel方法。而在onListen方法的入參中,有一個EventSink(其在Android是一個對象)。我們持有EventSink后,即可通過EventSink向Flutter端發送事件消息。
下面再來看下_sendPlatformMessage方法,其最終調用的是ui.window.sendPlatformMessage方法,該方法中會傳遞回調方法對象,在數據返回后會被回調從而得到結果數據。
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) {
final String error =
_sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
if (error != null)
throw Exception(error);
}
String _sendPlatformMessage(String name,
PlatformMessageResponseCallback callback,
ByteData data) native 'Window_sendPlatformMessage';
在以上代碼中ui.window.sendPlatformMessage()方法最終會調用Dart本地接口方法_sendPlatformMessage,這里可以將這個方法簡單理解為類似于java的JNI的方法,在c++層會調用"Window_sendPlatformMessage"對應的方法。至此,dart中的方法消息傳遞已經結束,
c++層是如何對方法調用消息進行傳遞的。這里不做具體分析,有興趣可以從Flutter engine源碼中分析
接下來我們開始分析java層接受到消息后的處理邏輯。
c++層通過調用flutterJNI的handlePlatformMessage方法將channel名稱、消息內容和響應ID傳給java層,我們來看一下FlutterJNI中的方法實現
private void handlePlatformMessage(@NonNull String channel, byte[] message, int replyId) {
if (this.platformMessageHandler != null) {
this.platformMessageHandler.handleMessageFromDart(channel, message, replyId);
}
}
此時會調用platformMessageHandler的handleMessageFromDart()方法,handleMessageFromDart 是PlatformMessageHandler 接口里面的一個方法 在DartMessenger 里面實現
public void handleMessageFromDart(@NonNull String channel, @Nullable byte[] message, int replyId) {
Log.v("DartMessenger", "Received message from Dart over channel '" + channel + "'");
//首先根據channel名稱從mMessageHandlers中查找對應的BinaryMessageHandler對象
BinaryMessageHandler handler = (BinaryMessageHandler)this.messageHandlers.get(channel);
if (handler != null) {
try {
Log.v("DartMessenger", "Deferring to registered handler to process message.");
ByteBuffer buffer = message == null ? null : ByteBuffer.wrap(message);
//如果找到則執行該對象的onMessage()方法,
handler.onMessage(buffer, new DartMessenger.Reply(this.flutterJNI, replyId));
} catch (Exception var6) {
Log.e("DartMessenger", "Uncaught exception in binary message listener", var6);
this.flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
} else {
Log.v("DartMessenger", "No registered handler for message. Responding to Dart with empty reply message.");
this.flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
}
我們主要看 mMessageHandlers中是怎么保存我們的channel名稱為flutter_method_channel 的對象的呢,我們看下開始所說的demo中的java模塊相關代碼,
public static String methodChannelName = "flutter_method_channel";
methodChannel.setMethodCallHandler(this);
@Override
public void onMethodCall(MethodCall methodCall, Result result) {
switch (methodCall.method) {
case "success":
//解析參數
String value = methodCall.argument("flutter");
// Toast.makeText(applicationContext, "" + value, Toast.LENGTH_SHORT).show();
result.success("A");
break;
default:
result.notImplemented();
}
}
創建一個名為flutter_method_channel 的MethodChannel對象,然后設置對應的MethodCallHandler對象,setMethodCallHandler的方法實現如下
@UiThread
public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
this.messenger.setMessageHandler(this.name, handler == null ? null : new MethodChannel.IncomingMethodCallHandler(handler));
}
其中的messenger就是實現了BinaryMessenger接口的對象 再來看 this.messenger.setMessageHandler ,setMessageHandler 是一個接口,主要看他在DartMessenger 的實現類
public void setMessageHandler(@NonNull String channel, @Nullable BinaryMessageHandler handler) {
if (handler == null) {
Log.v("DartMessenger", "Removing handler for channel '" + channel + "'");
this.messageHandlers.remove(channel);
} else {
Log.v("DartMessenger", "Setting handler for channel '" + channel + "'");
this.messageHandlers.put(channel, handler);
}
}
到此,我們發現在注冊插件方法中實現的 MethodCallHandler通過一系列操作被包裝到MethodChannel.IncomingMethodCallHandler對象中并設置進了mMessageHandlers中。那么我們上面所說的onMessage方法的調用即是MethodChannel.IncomingMethodCallHandler對象的方法,
private final class IncomingMethodCallHandler implements BinaryMessageHandler {
private final MethodChannel.MethodCallHandler handler;
IncomingMethodCallHandler(MethodChannel.MethodCallHandler handler) {
this.handler = handler;
}
@UiThread
public void onMessage(ByteBuffer message, final BinaryReply reply) {
//首先將從c++層傳遞過來的消息通過codec解碼為MethodCall對象
MethodCall call = MethodChannel.this.codec.decodeMethodCall(message);
try {
this.handler.onMethodCall(call, new MethodChannel.Result() {
public void success(Object result) {
reply.reply(MethodChannel.this.codec.encodeSuccessEnvelope(result));
}
public void error(String errorCode, String errorMessage, Object errorDetails) {
reply.reply(MethodChannel.this.codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
public void notImplemented() {
reply.reply((ByteBuffer)null);
}
});
} catch (RuntimeException var5) {
Log.e("MethodChannel#" + MethodChannel.this.name, "Failed to handle method call", var5);
reply.reply(MethodChannel.this.codec.encodeErrorEnvelope("error", var5.getMessage(), (Object)null));
}
}
}
方法中首先將從c++層傳遞過來的消息通過codec解碼為MethodCall對象,然后調用注冊的地方實現的MethodHandler的onMethodCall方法,該方法會接受flutter 傳遞的參數,
case "success":
//解析參數
String value = methodCall.argument("flutter");
// Toast.makeText(applicationContext, "" + value, Toast.LENGTH_SHORT).show();
result.success("A");
break;
然后調用result.success()方法,通過reply.reply(MethodChannel.this.codec.encodeSuccessEnvelope(result));將結果數據編碼后進行返回。reply方法中會調用
DartMessenger .this.mFlutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position());方法將響應結果返回,方法具體實現如下
public void reply(@Nullable ByteBuffer reply) {
if (this.done.getAndSet(true)) {
throw new IllegalStateException("Reply already submitted");
} else {
if (reply == null) {
this.flutterJNI.invokePlatformMessageEmptyResponseCallback(this.replyId);
} else {
this.flutterJNI.invokePlatformMessageResponseCallback(this.replyId, reply, reply.position());
}
}
}
最終會調用JNI方法將數據返回給c++層,
Dart層接收消息響應后的處理流程分析
通過以上Dart層傳遞消息分析可知PlatformMessageResponseCallback方法回調后對byte_buffer數據進行處理,通過completer.complete()方法完成返回數據,然后一步步返回到調用方法層,在異步方法中通過await等待數據返回后,再通過setState改變State中的變量值從而刷新頁面數據信息顯示到屏幕上。至此,整個flutter發消息給platform并接收消息處理的流程就完成了。
簡約調用關系圖如下
總結,整個消息發送和接收結果的流程分為以下幾步:
- Dart層通過以上提到的12種類型包含的類型數據進行編碼,然后通過dart的類似jni的本地接口方法傳遞給c++層;
- c++層通過持有java對象flutterJNI的方法調用將消息傳遞到java層;
- java層解碼接收到的消息,根據消息內容做指定的邏輯處理,得到結果后進行編碼通過jni方法將響應結果返回給c++層;
- c++層處理返回的響應結果,并將結果通過發送時保存的dart響應方法對象回調給Dart層;
Dart層通過回調方法對結果數據進行處理,然后通過codec解碼數據做后續的操作;