級別: ★★☆☆☆
標籤:「Flutter」「Platform」「Channel」
做者: snow
審校: QiShare團隊php
一、若是 Flutter 要獲取設備的電量信息怎麼辦?
二、若是 Flutter 要實時監控網絡狀態怎麼辦?
java
因爲 Flutter 特色以下:android
Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.c++
一、Flutter 是一個跨平臺的 UI 庫,專一於構建高效的 UI。
二、多平臺的支持,下圖是 Flutter 目前支持的平臺,每一個平臺的都有本身的平臺特性。
git
基於以上兩點,目前 Flutter 若是要和平臺相關的部分通訊須要有一個通道即 PlatformChannel。github
三種類型的 channel 都定義在 platform_channel.dart
中,從源碼中能夠看到三種 channel 都用到了如下三個屬性。web
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)channel 的名字,每一個 Flutter 應用可能有多個 channel,可是每一個 channel 必須有一個惟一的名字。shell
codec 用來對數據編碼解碼,以便兩端能夠正確讀取數據。 微信
用於發送數據網絡
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
方法時,根據參數返回信息。
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 端調用便可。
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。當收到消息時發一個回覆。
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 端直接發送消息便可。
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 端發送一次消息。
以下圖,在 Dart 與 Platform 通訊過程當中,經過 channel name 找到對方,而後把消息經過 codec 進行編解碼,最後經過 binaryMessenger 進行發送。
@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;
}
複製代碼
@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;
}
複製代碼
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';
複製代碼
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();
}
複製代碼
WindowClient* client() const { return client_; }
複製代碼
class RuntimeController final : public WindowClient {
...
// |WindowClient|
void HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) override;
...
}
複製代碼
void RuntimeController::HandlePlatformMessage(
fml::RefPtr<PlatformMessage> message) {
client_.HandlePlatformMessage(std::move(message));
}
複製代碼
RuntimeDelegate& p_client
複製代碼
class Engine final : public RuntimeDelegate {
...
// |RuntimeDelegate|
void HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) override;
...
}
複製代碼
void Engine::HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) {
if (message->channel() == kAssetChannel) {
HandleAssetPlatformMessage(std::move(message));
} else {
delegate_.OnEngineHandlePlatformMessage(std::move(message));
}
}
複製代碼
// |Engine::Delegate|
void OnEngineHandlePlatformMessage( fml::RefPtr<PlatformMessage> message) override;
複製代碼
// |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));
}
});
}
複製代碼
class PlatformViewAndroid final : public PlatformView {
...
// |PlatformView|
void HandlePlatformMessage( fml::RefPtr<flutter::PlatformMessage> message) override;
...
}
複製代碼
// |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);
}
}
複製代碼
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;
}
複製代碼
private void handlePlatformMessage(@NonNull String channel, byte[] message, int replyId) {
if (this.platformMessageHandler != null) {
this.platformMessageHandler.handleMessageFromDart(channel, message, replyId);
}
}
複製代碼
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);
}
}
複製代碼
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
奇舞週刊