級別: ★★☆☆☆
標籤:「Flutter」「Platform」「Channel」
作者: snow
審校: QiShare團隊
1. 為什麼要有 PlatformChannel
1、如果 Flutter 要獲取裝置的電量資訊怎麼辦?
2、如果 Flutter 要實時監控網路狀態怎麼辦?
由於 Flutter 特點如下:
Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.
1、Flutter 是一個跨平臺的 UI 庫,專注於構建高效的 UI。
2、多平臺的支援,下圖是 Flutter 目前支援的平臺,每個平臺的都有自己的平臺特性。
基於以上兩點,目前 Flutter 如果要和平臺相關的部分通訊需要有一個通道即 PlatformChannel。
2. 架構圖
3. PlatformChannel 型別
- BasicMessageChannel:用於資料傳遞。platform 和 dart 可互相傳遞資料(asynchronous message passing)
- MethodChannel:用於傳遞方法呼叫。platform 和 dart 可互相呼叫方法(asynchronous method calls)
- EventChannel:用於資料流通訊。建立連線之後,platform 傳送訊息,dart 接收訊息(event streams)
三種型別的 channel 都定義在 platform_channel.dart
中,從原始碼中可以看到三種 channel 都用到了以下三個屬性。
name
:String 型別,表示 channel 的名字,全域性唯一(The logical channel on which communication happens)codec
:MessageCodec 型別,訊息的編碼解碼器(The message codec used by this channel)binaryMessenger
:BinaryMessenger型別,用於傳送資料(The messenger used by this channel to send platform messages)
3.1 channel name
channel 的名字,每個 Flutter 應用可能有多個 channel,但是每個 channel 必須有一個唯一的名字。
3.2 codec
codec 用來對資料編碼解碼,以便兩端可以正確讀取資料。
3.3 binaryMessenger
用於傳送資料
4. PlatformChannel 使用
4.1 MethodChannel
- Dart 呼叫 Android 方法
method_channel_page.dart 主要程式碼
第一步
static const methodChannel = MethodChannel("method_channel_sample");
第二步
Future<dynamic> getUserInfo(String method, {String userName}) async {
return await methodChannel.invokeMethod(method, userName);
}
第三步
MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: new Text('獲取 snow 使用者資訊'),
onPressed: () {
getUserInfo("getInfo", userName: "snow")
..then((result) {
setState(() {
messageFromNative = result;
});
});
},
),
複製程式碼
MainActivity.java 主要程式碼
private void addMethodChannel() {
mMethodChannel = new MethodChannel(getFlutterView(), "method_channel_sample");
mMethodChannel.setMethodCallHandler((methodCall, result) -> {
String method = methodCall.method;
if ("getInfo".equals(method)) {
String userName = (String) methodCall.arguments;
if (userName.equals("rocx")) {
String user = "name:rocx, age:18";
result.success(user);
} else {
result.success("user not found");
invokeSayHelloMethod();
}
}
});
}
複製程式碼
從以上程式碼可以看出
Dart 呼叫 Android 程式碼分三步。首先在 Dart 端定義 MethodChannel 名字為 method_channel_sample
。然後定義getUserInfo
方法,傳入要呼叫的方法名和引數。最後點選按鈕執行方法,獲取使用者資訊。
在 Android 端定一個 MethodChannel 名字和 Dart 端保持一致。設定 MethodCallHandler。當呼叫的是getInfo
方法時,根據引數返回資訊。
- Android 呼叫 Dart 方法
MainActivity.java 主要程式碼
private void invokeSayHelloMethod() {
mMethodChannel.invokeMethod("sayHello", "", new MethodChannel.Result() {
@Override
public void success(Object o) {
Toast.makeText(MainActivity.this, o.toString(), Toast.LENGTH_LONG).show();
}
@Override
public void error(String s, String s1, Object o) {
}
@Override
public void notImplemented() {
}
});
}
複製程式碼
method_channel_page.dart 主要程式碼
Future<dynamic> addHandler(MethodCall call) async {
switch (call.method) {
case "sayHello":
return "Hello from Flutter";
break;
}
}
@override
void initState() {
super.initState();
methodChannel.setMethodCallHandler(addHandler);
}
複製程式碼
從程式碼可以看出,在 Dart 端設定 MethodCallHandler 然後在 Android 端呼叫即可。
4.2 BasicMessageChannel
- Dart 向 Android 傳送訊息
basic_message_channel_page.dart 主要程式碼
第一步
static const basicMessageChannel = BasicMessageChannel(
"basic_message_channel_sample", StandardMessageCodec());
第二步
Future<dynamic> sayHelloToNative(String message) async {
String reply = await basicMessageChannel.send(message);
setState(() {
msgReplyFromNative = reply;
});
return reply;
}
第三步
MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: new Text('say hello to native'),
onPressed: () {
sayHelloToNative("hello");
},
),
複製程式碼
MainActivity.java 主要程式碼
private void addBasicMessageChannel() {
mBasicMessageChannel = new BasicMessageChannel<>(getFlutterView(), "basic_message_channel_sample", StandardMessageCodec.INSTANCE);
mBasicMessageChannel.setMessageHandler((object, reply) -> {
reply.reply("receive " + object.toString() + " from flutter");
mBasicMessageChannel.send("native say hello to flutter");
});
}
複製程式碼
從以上程式碼可以看出
Dart 向 Android 傳送訊息依然分為三步。首先在 Dart 端定義 BasicMessageChannel 名字為 basic_message_channel_sample
。然後定義傳送訊息的方法sayHelloToNative
。最後點選按鈕向 Android 端傳送訊息。
在 Android 端定一個 BasicMessageChannel 名字和 Dart 端保持一致。設定 MethodCallHandler。當收到訊息時發一個回覆。
- Android 向 Dart 傳送訊息
MainActivity.java 主要程式碼
mBasicMessageChannel.send("native say hello to flutter");
複製程式碼
basic_message_channel_page.dart 主要程式碼
Future<dynamic> addHandler(Object result) async {
setState(() {
msgReceiveFromNative = result.toString();
});
}
void addMessageListener() {
basicMessageChannel.setMessageHandler(addHandler);
}
@override
void initState() {
super.initState();
addMessageListener();
}
複製程式碼
從程式碼可以看出,在 Dart 端設定 MessageHandler 然後在 Android 端直接傳送訊息即可。
4.3 EventChannel
event_channel_page.dart 主要程式碼
第一步
static const eventChannel = EventChannel("event_channel_sample");
void _onEvent(Object event) {
setState(() {
if (_streamSubscription != null) {
eventMessage = event.toString();
}
});
}
void _onError(Object error) {
setState(() {
if (_streamSubscription != null) {
eventMessage = "error";
}
});
}
@override
void initState() {
super.initState();
eventMessage = "";
第二步
_streamSubscription = eventChannel
.receiveBroadcastStream()
.listen(_onEvent, onError: _onError);
}
複製程式碼
MainActivity.java 主要程式碼
private void addEventChannel() {
mEventChannel = new EventChannel(getFlutterView(), "event_channel_sample");
mEventChannel.setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
task = new TimerTask() {
@Override
public void run() {
runOnUiThread(() -> eventSink.success("i miss you " + System.currentTimeMillis()));
}
};
timer = new Timer();
timer.schedule(task, 2000, 3000);
}
@Override
public void onCancel(Object o) {
task.cancel();
timer.cancel();
task = null;
timer = null;
}
});
}
複製程式碼
Dart 接受 Android stream event。首先在 Dart 端定義 EventChannel 名字為 event_channel_sample
。然後設定receiveBroadcastStream
監聽,當 Android 端有訊息發過來會回撥_onEvent
方法。
在 Android 端啟動一個定時器,每隔3s向 Dart 端傳送一次訊息。
4.4 總結
如下圖,在 Dart 與 Platform 通訊過程中,通過 channel name 找到對方,然後把訊息通過 codec 進行編解碼,最後通過 binaryMessenger 進行傳送。
5. 原始碼分析-以 MethodChannel 為例
5.1 呼叫 MethodChannel 的 invokeMethod 方法,會呼叫到 binaryMessenger.send 方法。即 binaryMessenger.send 傳入 channel name 和編碼好的引數。
@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;
}
複製程式碼
5.2 binary_messenger.dart 的 send 方法會呼叫當前物件的 _sendPlatformMessage 方法,最終會呼叫 window.sendPlatformMessage 方法。
@override
Future<ByteData> send(String channel, ByteData message) {
final MessageHandler handler = _mockHandlers[channel];
if (handler != null)
return handler(message);
return _sendPlatformMessage(channel, message);
}
複製程式碼
Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
final Completer<ByteData> completer = Completer<ByteData>();
// ui.window is accessed directly instead of using ServicesBinding.instance.window
// because this method might be invoked before any binding is initialized.
// This issue was reported in #27541. It is not ideal to statically access
// ui.window because the Window may be dependency injected elsewhere with
// a different instance. However, static access at this location seems to be
// the least bad option.
ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message response callback'),
));
}
});
return completer.future;
}
複製程式碼
5.3 在 window.dart 中又呼叫了 native 方法 _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';
複製程式碼
5.4 接下來進入 engine 中的 window.cc,可以看到最終呼叫的是 dart_state->window()->client()->HandlePlatformMessage。
void Window::RegisterNatives(tonic::DartLibraryNatives* natives) {
natives->Register({
{"Window_defaultRouteName", DefaultRouteName, 1, true},
{"Window_scheduleFrame", ScheduleFrame, 1, true},
{"Window_sendPlatformMessage", _SendPlatformMessage, 4, true},
{"Window_respondToPlatformMessage", _RespondToPlatformMessage, 3, true},
{"Window_render", Render, 2, true},
{"Window_updateSemantics", UpdateSemantics, 2, true},
{"Window_setIsolateDebugName", SetIsolateDebugName, 2, true},
{"Window_reportUnhandledException", ReportUnhandledException, 2, true},
{"Window_setNeedsReportTimings", SetNeedsReportTimings, 2, true},
});
}
複製程式碼
void _SendPlatformMessage(Dart_NativeArguments args) {
tonic::DartCallStatic(&SendPlatformMessage, args);
}
複製程式碼
Dart_Handle SendPlatformMessage(Dart_Handle window,
const std::string& name,
Dart_Handle callback,
Dart_Handle data_handle) {
UIDartState* dart_state = UIDartState::Current();
if (!dart_state->window()) {
return tonic::ToDart(
"Platform messages can only be sent from the main isolate");
}
fml::RefPtr<PlatformMessageResponse> response;
if (!Dart_IsNull(callback)) {
response = fml::MakeRefCounted<PlatformMessageResponseDart>(
tonic::DartPersistentValue(dart_state, callback),
dart_state->GetTaskRunners().GetUITaskRunner());
}
if (Dart_IsNull(data_handle)) {
dart_state->window()->client()->HandlePlatformMessage(
fml::MakeRefCounted<PlatformMessage>(name, response));
} else {
tonic::DartByteData data(data_handle);
const uint8_t* buffer = static_cast<const uint8_t*>(data.data());
dart_state->window()->client()->HandlePlatformMessage(
fml::MakeRefCounted<PlatformMessage>(
name, std::vector<uint8_t>(buffer, buffer + data.length_in_bytes()),
response));
}
return Dart_Null();
}
複製程式碼
5.5 我們進入 window.h 中找到 client 其實是 WindowClient。
WindowClient* client() const { return client_; }
複製程式碼
5.6 在 runtime_controller.h 中可以看到 RuntimeController 是 WindowClient 的實際實現,呼叫的是 RuntimeController 的 HandlePlatformMessage 方法。
class RuntimeController final : public WindowClient {
...
// |WindowClient|
void HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) override;
...
}
複製程式碼
5.7 在 runtime_controller.cc 中,HandlePlatformMessage 呼叫了 client_ 的 HandlePlatformMessage 方法,client_ 實際是代理物件 RuntimeDelegate。
void RuntimeController::HandlePlatformMessage(
fml::RefPtr<PlatformMessage> message) {
client_.HandlePlatformMessage(std::move(message));
}
複製程式碼
RuntimeDelegate& p_client
複製程式碼
5.8 engine.h 是 RuntimeDelegate 的具體實現類。
class Engine final : public RuntimeDelegate {
...
// |RuntimeDelegate|
void HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) override;
...
}
複製程式碼
5.9 engine.cc 中呼叫了 delegate_ 的 OnEngineHandlePlatformMessage 方法。
void Engine::HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) {
if (message->channel() == kAssetChannel) {
HandleAssetPlatformMessage(std::move(message));
} else {
delegate_.OnEngineHandlePlatformMessage(std::move(message));
}
}
複製程式碼
5.10 shell.h 是 Engine 的代理。
// |Engine::Delegate|
void OnEngineHandlePlatformMessage(
fml::RefPtr<PlatformMessage> message) override;
複製程式碼
5.11 呼叫流程又進入了 shell.cc 的 HandleEngineSkiaMessage 方法,把消費放到 TaskRunner 中。
// |Engine::Delegate|
void Shell::OnEngineHandlePlatformMessage(
fml::RefPtr<PlatformMessage> message) {
FML_DCHECK(is_setup_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
if (message->channel() == kSkiaChannel) {
HandleEngineSkiaMessage(std::move(message));
return;
}
task_runners_.GetPlatformTaskRunner()->PostTask(
[view = platform_view_->GetWeakPtr(), message = std::move(message)]() {
if (view) {
view->HandlePlatformMessage(std::move(message));
}
});
}
複製程式碼
5.12 當 task 執行是會呼叫 platform_view_android.h 的 HandlePlatformMessage 方法。
class PlatformViewAndroid final : public PlatformView {
...
// |PlatformView|
void HandlePlatformMessage(
fml::RefPtr<flutter::PlatformMessage> message) override;
...
}
複製程式碼
5.13 在 platform_view_android.cc 的 HandlePlatformMessage 中,開始通過 jni 呼叫 java 端的方法,java_channel 即要找的 channel。
// |PlatformView|
void PlatformViewAndroid::HandlePlatformMessage(
fml::RefPtr<flutter::PlatformMessage> message) {
JNIEnv* env = fml::jni::AttachCurrentThread();
fml::jni::ScopedJavaLocalRef<jobject> view = java_object_.get(env);
if (view.is_null())
return;
int response_id = 0;
if (auto response = message->response()) {
response_id = next_response_id_++;
pending_responses_[response_id] = response;
}
auto java_channel = fml::jni::StringToJavaString(env, message->channel());
if (message->hasData()) {
fml::jni::ScopedJavaLocalRef<jbyteArray> message_array(
env, env->NewByteArray(message->data().size()));
env->SetByteArrayRegion(
message_array.obj(), 0, message->data().size(),
reinterpret_cast<const jbyte*>(message->data().data()));
message = nullptr;
// This call can re-enter in InvokePlatformMessageXxxResponseCallback.
FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),
message_array.obj(), response_id);
} else {
message = nullptr;
// This call can re-enter in InvokePlatformMessageXxxResponseCallback.
FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),
nullptr, response_id);
}
}
複製程式碼
5.14 在 platform_view_android_jni.cc 中可以看到 g_handle_platform_message_method 就是 FindClass("io/flutter/embedding/engine/FlutterJNI") 類的 handlePlatformMessage 方法。至此 engine 程式碼執行結束。
static jmethodID g_handle_platform_message_method = nullptr;
void FlutterViewHandlePlatformMessage(JNIEnv* env,
jobject obj,
jstring channel,
jobject message,
jint responseId) {
env->CallVoidMethod(obj, g_handle_platform_message_method, channel, message,
responseId);
FML_CHECK(CheckException(env));
}
複製程式碼
g_handle_platform_message_method =
env->GetMethodID(g_flutter_jni_class->obj(), "handlePlatformMessage",
"(Ljava/lang/String;[BI)V");
複製程式碼
g_flutter_jni_class = new fml::jni::ScopedJavaGlobalRef<jclass>(
env, env->FindClass("io/flutter/embedding/engine/FlutterJNI"));
if (g_flutter_jni_class->is_null()) {
FML_LOG(ERROR) << "Failed to find FlutterJNI Class.";
return false;
}
複製程式碼
5.15 在 FlutterJNI 中呼叫了 this.platformMessageHandler.handleMessageFromDart 方法。也就是 DartMessenger 的 handleMessageFromDart 方法。
private void handlePlatformMessage(@NonNull String channel, byte[] message, int replyId) {
if (this.platformMessageHandler != null) {
this.platformMessageHandler.handleMessageFromDart(channel, message, replyId);
}
}
複製程式碼
5.16 DartMessenger 中 messageHandlers 通過 channel 名找到對應的 handler 進行處理,這個 handler 就是我們在 java 程式碼裡通過 channel 設定的,整個呼叫流程完成。
public void handleMessageFromDart(@NonNull String channel, @Nullable byte[] message, int replyId) {
Log.v("DartMessenger", "Received message from Dart over channel '" + channel + "'");
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);
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);
}
}
複製程式碼
Demo 地址
參考資源
Writing custom platform-specific code
platform channel 官方示例
深入理解Flutter Platform Channel
瞭解更多iOS及相關新技術,請關注我們的公眾號:
小編微信:可加並拉入《QiShare技術交流群》。
關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)
推薦文章:
開發沒切圖怎麼辦?向量圖示(iconFont)上手指南
DarkMode、WKWebView、蘋果登入是否必須適配?
iOS 接入 Google、Facebook 登入(二)
iOS 接入 Google、Facebook 登入(一)
Nginx 入門實戰 iOS中的3D變換(二)
iOS中的3D變換(一)
WebSocket 雙端實踐(iOS/ Golang)
今天我們來聊一聊WebSocket(iOS/Golang)
奇舞團安卓團隊——aTaller
奇舞週刊