QiShare team QiShare team QiShare team QiShare team QiShare team QiShare team QiShare team


1. Why PlatformChannel

1. What if the Flutter needs to obtain the electric information of the device? 2. What if Flutter wants to monitor network status in real time?

Due to the following characteristics of Flutter:

Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.

Flutter is a cross-platform UI library focused on building efficient UIs. 2. Multiple platform support. The following diagram shows the platforms currently supported by Flutter, each with its own platform features.



Based on the above two points, Flutter currently needs a PlatformChannel to communicate with platform-related parts.

2. The architecture diagram

3. PlatformChannel type

  • BasicMessageChannel: Used for data transfer. Asynchronous message Passing Between Platform and DART
  • MethodChannel: Used to pass method calls. Asynchronous method calls platform and DART can call asynchronous method calls
  • EventChannel: Used for data flow messages. Once the connection is established, Platform sends messages and DART receives them (Event Streams)

Dart, platform_channel.dart, platform_channel.dart, platform_channel.dart, platform_channel.dart, platform_channel.dart, platform_channel.dart, platform_channel.dart

  • nameThe logical channel on which communication happens
  • codec: MessageCodec, The message codec used by this channel
  • binaryMessenger: BinaryMessenger type, used to send data (The messenger used by this channel to send platform messages)

3.1 the channel name

The name of a channel. Each Flutter application may have multiple channels, but each channel must have a unique name.

3.2 the codec

Codec is used to encode and decode data so that both ends can read it correctly.

3.3 binaryMessenger

Used to send data

4. PlatformChannel use

4.1 MethodChannel

  • Dart calls Android methods

Method_channel_page.dart Main code

The first stepstatic const methodChannel = MethodChannel("method_channel_sample"); The second step is the Future"dynamic> getUserInfo(String method, {String userName}) async {
  return awaitmethodChannel.invokeMethod(method, userName); } MaterialButton(color: color.blue, textColor: color.white, child: color.white)new Text('Get snow user information'),
  onPressed: () {
    getUserInfo("getInfo", userName: "snow").. then((result) { setState(() { messageFromNative = result; }); }); },),Copy the code

Mainactivity.java main code

private void addMethodChannel(a) {
    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(); }}}); }Copy the code

As you can see from the above code, Dart calls Android code in three steps. First you define a MethodChannel on the Dart side named method_channel_SAMPLE. You then define the getUserInfo method, passing in the name and parameters of the method to call. Finally, click the button to execute the method to obtain user information. Give the Android side a MethodChannel name consistent with the Dart side. Set the MethodCallHandler. When the getInfo method is called, information is returned based on the arguments.

  • Android calls the Dart method

Mainactivity.java main code

private void invokeSayHelloMethod(a) {
    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(a) {}}); }Copy the code

Method_channel_page.dart Main code

Future<dynamic> addHandler(MethodCall call) async {
  switch (call.method) {
    case "sayHello":
      return "Hello from Flutter";
      break; }}@override
void initState() {
  super.initState();

  methodChannel.setMethodCallHandler(addHandler);
}
Copy the code

As you can see from the code, you can set up the MethodCallHandler on the Dart side and call it on the Android side.

4.2 BasicMessageChannel

  • Dart sends a message to Android

Basic_message_channel_page.dart Main code

The first stepstatic const basicMessageChannel = BasicMessageChannel(
      "basic_message_channel_sample", StandardMessageCodec()); The second step is the Future"dynamic> sayHelloToNative(String message) async {
  String reply = await basicMessageChannel.send(message);

  setState(() {
    msgReplyFromNative = reply;
  });

  returnreply; } MaterialButton(color: color.blue, textColor: color.white, child: color.white)new Text('say hello to native'),
  onPressed: () {
    sayHelloToNative("hello"); },),Copy the code

Mainactivity.java main code

private void addBasicMessageChannel(a) {
    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");
    });
}

Copy the code

As you can see from the above code, Dart sends a message to Android in three steps. First, define the BasicMessageChannel name basic_message_channel_SAMPLE on the Dart side. Then define the method to send the message, sayHelloToNative. Finally, click the button to send a message to the Android terminal. Create a BasicMessageChannel on the Android end with the same name as the Dart end. Set the MethodCallHandler. Send a reply when you receive a message.

  • Android sends a message to Dart

Mainactivity.java main code

mBasicMessageChannel.send("native say hello to flutter");
Copy the code

Basic_message_channel_page.dart Main code

Future<dynamic> addHandler(Object result) async {
  setState(() {
    msgReceiveFromNative = result.toString();
  });
}

void addMessageListener() {
  basicMessageChannel.setMessageHandler(addHandler);
}

@override
void initState() {
  super.initState();
  addMessageListener();
}

Copy the code

As you can see from the code, set up MessageHandler on the Dart side and send the message directly on the Android side.

4.3 the EventChannel

Dart Main code

The first stepstatic 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); }Copy the code

Mainactivity.java main code

private void addEventChannel(a) {
    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(a) {
                    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; }}); }Copy the code

Dart accepts Android Stream events. First define the EventChannel name on the Dart side as event_channel_SAMPLE. Then set the receiveBroadcastStream listener to call the _onEvent method when a message is sent from Android. Start a timer on the Android terminal and send a message to the Dart terminal every 3s.

4.4 summarize

As shown in the figure below, Dart finds Platform through channel name, codecs the message, and sends it through binaryMessenger.

5. Source code analysis – Take MethodChannel as an example

5.1 Calling the invokeMethod method on MethodChannel calls the binaryMessenger. Send method. BinaryMessenger. Send passes in the channel name and encoded parameters.
  @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;
  }
Copy the code
5.2 binary_messenger. Dart the send method will call the current object _sendPlatformMessage method, will eventually call window. SendPlatformMessage method.
  @override
  Future<ByteData> send(String channel, ByteData message) {
    final MessageHandler handler = _mockHandlers[channel];
    if(handler ! =null)
      return handler(message);
    return _sendPlatformMessage(channel, message);
  }
Copy the code
  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;
  }
Copy the code
5.3 Native _sendPlatformMessage is called in window.dart.
  void sendPlatformMessage(String name,
                           ByteData data,
                           PlatformMessageResponseCallback callback) {
    final String error =
        _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
    if(error ! =null)
      throw Exception(error);
  }
Copy the code
  String _sendPlatformMessage(String name,
                              PlatformMessageResponseCallback callback,
                              ByteData data) native 'Window_sendPlatformMessage';
Copy the code
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}}); }Copy the code
void _SendPlatformMessage(Dart_NativeArguments args) {
  tonic::DartCallStatic(&SendPlatformMessage, args);
}
Copy the code
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();
}
Copy the code

Window. Cc the source code

5.5 We enter window.h and find that client is actually WindowClient.
  WindowClient* client(a) const { return client_; }
Copy the code

Window. H source

5.6 You can see in runtime_controller.h that RuntimeController is the actual implementation of WindowClient. The HandlePlatformMessage method of RuntimeController is called.
class RuntimeController final : public WindowClient {

...
  // |WindowClient|
  void HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) override; . }Copy the code

Runtime_controller. H source

5.7 In runtime_controller.cc, HandlePlatformMessage calls client_’s HandlePlatformMessage method, Client_ is actually a RuntimeDelegate for the proxy object.
void RuntimeController::HandlePlatformMessage(
    fml::RefPtr<PlatformMessage> message) {
  client_.HandlePlatformMessage(std::move(message));
}
Copy the code
RuntimeDelegate& p_client
Copy the code

Runtime_controller. Cc the source code

5.8 Engine. h is the concrete implementation class for RuntimeDelegate.
class Engine final : public RuntimeDelegate {
...
  // |RuntimeDelegate|
  void HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) override; . }Copy the code

H engine. The source code

5.9 engine. Cc call delegate_ OnEngineHandlePlatformMessage method.
void Engine::HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) {
  if (message->channel() == kAssetChannel) {
    HandleAssetPlatformMessage(std::move(message));
  } else {
    delegate_.OnEngineHandlePlatformMessage(std::move(message)); }}Copy the code

Engine. Cc the source code

5.10 shell. H is a proxy for Engine.
  // |Engine::Delegate|
  void OnEngineHandlePlatformMessage( fml::RefPtr
       
         message)
        override;
Copy the code

Shell. H source

The 5.11 call flow goes into the HandleEngineSkiaMessage method of shell.cc and puts the consumption into 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)); }}); }Copy the code

Shell. Cc the source code

5.12 The HandlePlatformMessage method of platform_view_android.h is called when the task is executed.
class PlatformViewAndroid final : public PlatformView {
...
  // |PlatformView|
  void HandlePlatformMessage( fml::RefPtr
       <:platformmessage>
         message)
        override; . }Copy the code

Platform_view_android. H source

5.13 In the HandlePlatformMessage of platform_view_android.cc, start calling java-side methods through JNI, java_channel is the channel to find.
// |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); }}Copy the code

Platform_view_android. Cc the source code

G_handle_platform_message_method can be found in platform_view_android_jni.cc FindClass (” IO/flutter/embedding/engine/FlutterJNI “) class handlePlatformMessage method. At this point the engine code completes execution.
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));
}
Copy the code
  g_handle_platform_message_method =
      env->GetMethodID(g_flutter_jni_class->obj(), "handlePlatformMessage"."(Ljava/lang/String; [BI)V");
Copy the code
  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;
  }
Copy the code

Platform_view_android_jni. Cc the source code

5.15 in FlutterJNI calls this. PlatformMessageHandler. HandleMessageFromDart method. The handleMessageFromDart method for DartMessenger.
    private void handlePlatformMessage(@NonNull String channel, byte[] message, int replyId) {
        if (this.platformMessageHandler ! =null) {
            this.platformMessageHandler.handleMessageFromDart(channel, message, replyId); }}Copy the code
5.16 In DartMessenger, messageHandlers find the corresponding handler through the channel name for processing. This handler is set in Java code through the channel, and the whole call process is completed.
    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); }}Copy the code

The Demo address

Flutter_platform_channel use

The resources

Write custom platform-specific code Platform Channel


To learn more about iOS and related new technologies, please follow our official account:

You can add the following xiaobian wechat, and note to join the QiShare technical exchange group, xiaobian will invite you to join the QiShare technical Exchange Group.

QiShare(Simple book) QiShare(digging gold) QiShare(Zhihu) QiShare(GitHub) QiShare(CocoaChina) QiShare(StackOverflow) QiShare(wechat public account)

Recommended article: How to develop without cutting map? Vector icon (iconFont) Getting started with guide DarkMode, WKWebView, Apple login must be adapted? IOS Access To Google and Facebook login (2) iOS access to Google and Facebook login (1) Nginx Getting started 3D transformation in iOS (2) 3D transformation in iOS (1) WebSocket dual-end practice (iOS/ Golang) Today we are going to talk about WebSocket (iOS/Golang) strange dance team Android team — aTaller strange dance weekly