级别: ★★☆☆☆
标签:「Flutter」「Platform」「Channel」
作者: snow
审校: QiShare团队
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。
三种类型的 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)channel 的名字,每个 Flutter 应用可能有多个 channel,但是每个 channel 必须有一个唯一的名字。
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
奇舞周刊