1. Architecture Overview

Messages are transmitted between Native (host) and client via platform channels, as shown in the following figure:

Messages are delivered asynchronously to ensure that the user interface responds properly. Either the native sends a message to the flutter, or the flutter sends a message to the native.

In A FLUTTER, a MethodChannel can send messages corresponding to method calls. On native platforms, MethodChannel can receive method calls on Android and return results. These classes help us develop platform plug-ins with very little code.

Platform channel data type support and codecs

Platform channels can encode and decode messages using provided codecs that support efficient binary serialization of simple JSON-like values, such as booleans, numbers, strings, byte buffers, and lists and mappings of these. As you send and receive values, they are automatically serialized and deserialized.

The following table shows how to receive the Dart value on the platform side and vice versa:

As for codecs, the Android side provides the following four.

  • **BinaryCodec: ** is the simplest codec that returns the same type of value as the input parameter, both in binary format (ByteBuffer). Since BinaryCodec does nothing during encoding and decoding, it just returns the binary data exactly as it was. So the data that is passed is not copied when it is encoded or decoded, which is useful when large amounts of data are passed. For example, upload an image from the Android side to the Flutter side display.
  • **StandardMessageCodec: ** Is the default codec for BasicMessageChannel, supporting basic data types, lists, dictionaries, etc. Data is written to a ByteArrayOutputStream and then written to a ByteBuffer. When decoding, data is read directly from ByteBuffer.
  • **StandardMethodCodec: ** is a package based on StandardMessageCodec. Is the default codec for MethodChannel and EventChannel.
  • **StringCodec: ** is used to encode strings and binary data in utF-8 format. The encoding converts the String into a byte array, which is then written to a ByteBuffer. When decoding, data is read directly from ByteBuffer
  • **JSONMessageCodec: ** Internal calls to StringCodec for codec.
  • **JSONMethodCodec: ** Encapsulation based on JSONMessageCodec. You can use this in MethodChannel and EventChannel.

A ByteBuffer is a class in Nio that is exactly what its name suggests — an area that stores bytes. It has two implementation classes — DirectByteBuffer, which creates an area in memory to store data, and HeapByteBuffer, which creates an area in the JVM heap to store data. Therefore, in order for the data to communicate with HeapByteBuffer in DirectByteBuffer, a copy is required.

3. Communication mode

Now that we’ve covered some of the basics of Android’s ability to communicate with Flutter, let’s look at how Android communicates with Flutter.

There are four ways to communicate between Android and Flutter.

  1. Since a string route is passed when a flutter page is initialized, we can use route as an argument to pass whatever data we want to pass. This mode supports only one-way data transfer and the data type can only be a string, with no return value.
  2. This is implemented through EventChannel, which only supports one-way data transmission and has no return value.
  3. This is done with a MethodChannel, which supports two-way data passing with a return value.
  4. This is achieved through BasicMessageChannel, which supports bidirectional data transfer and returns a value.

Here is a look at the use of these methods.

3.1. Pass values during initialization

This method is mainly used to create the route of flutter page transmission. The author thinks that this method is cheating, but it can still be used to transmit data. It is very simple to use, the code is as follows.

Let’s start with the Android code.

// The third argument can be replaced with the desired string. FlutterView flutterView = Flutter.createView(this, getLifecycle(),"route");
Copy the code

In Flutter, all we need to do is get the value by using the following code.

void main() => runApp(MyApp( initParams: window.defaultRouteName, )); class MyApp extends StatelessWidget { final String initParams; // route MyApp({Key Key, @required this.initparams}) : super(Key: Key); @override Widget build(BuildContext context) {... }}Copy the code

In this way, Android passes data to the flutter when it initializes the flutter. Since runApp is only called once, this method can only pass data once and the data must be a string.

Using the window API requires importing the dart: UI package

3.2, the EventChannel

EventChannel is a one-way communication method by which a native sends data to a flutter. The flutter cannot return any data to the native. It is mainly used by Native to send changes in mobile phone power, network connection, gyroscope and sensor to flutter. It can be used as follows.

Let’s start with the Android code.

public class EventChannelPlugin implements EventChannel.StreamHandler {

    private static final String TAG = EventChannelPlugin.class.getSimpleName();
    private EventChannel.EventSink eventSink;
    private Activity activity;

    static EventChannelPlugin registerWith(FlutterView flutterView) {
        EventChannelPlugin plugin = new EventChannelPlugin(flutterView);
        new EventChannel(flutterView, "EventChannelPlugin").setStreamHandler(plugin);
        return plugin;

    }

    private EventChannelPlugin(FlutterView flutterView) {
        this.activity = (Activity) flutterView.getContext();
    }

    void send(Object params) {
        if(eventSink ! = null) { eventSink.success(params); } } void sendError(String str1, String str2, Object params) {if(eventSink ! = null) { eventSink.error(str1, str2, params); } } voidcancel() {
        if(eventSink ! = null) { eventSink.endOfStream(); }} // The first argument is the value returned when the flutter initializes EventChannel, @Override public void onListen(Object O, eventChannel.eventSink EventSink) {this.eventsink = EventSink; Log.i(TAG,"eventSink:" + eventSink);
        Log.i(TAG, "Object:" + o.toString());
        Toast.makeText(activity, "OnListen - obj." + o, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onCancel(Object o) {
        Log.i(TAG, "onCancel:" + o.toString());
        Toast.makeText(activity, "OnCancel - obj."+ o, Toast.LENGTH_SHORT).show(); this.eventSink = null; }}Copy the code

The author of the Android side of the code to do a simple package, or very good understanding. Here is the flutter code implementation.

class _MyHomePageState extends State<MyHomePage> {
  EventChannel _eventChannelPlugin = EventChannel("EventChannelPlugin");
  StreamSubscription _streamSubscription;
  @override
  void initState() {
    _streamSubscription = _eventChannelPlugin
         //["abc", 123, "Hello"ReceiveBroadcastStream ([receiveBroadcastStream([]))"abc", 123, "Hello"])
        .listen(_onToDart, onError: _onToDartError, onDone: _onDone);
    super.initState();
  }

  @override
  void dispose() {
    if(_streamSubscription ! = null) { _streamSubscription.cancel(); _streamSubscription = null; } super.dispose(); } // Native sends normal data void _onToDart(message) {print(message); } // When native fails, send data void _onToDartError(error) {print(error); } // This method is called when native sends data, and void is called every time it sends data_onDone() {
    print("Message delivered.");
  }

  @override
  Widget build(BuildContext context) {...}
}
Copy the code

This is the code that communicates with flutter via EventChannel. Call the Send method of the EventChannelPlugin to send data to flutter.

3.3, MethodChannel

MethodChannel is a method by which native and flutter communicate with each other. As the name suggests, this method calls the corresponding methods in the flutter and has a return value. It can be used as follows.

First, look at the code implementation on Android.

public class MethodChannelPlugin implements MethodChannel.MethodCallHandler {

    private Activity activity;
    private MethodChannel channel;

    public static MethodChannelPlugin registerWith(FlutterView flutterView) {
        MethodChannel channel = new MethodChannel(flutterView, "MethodChannelPlugin");
        MethodChannelPlugin methodChannelPlugin = new MethodChannelPlugin((Activity) flutterView.getContext(), channel);
        channel.setMethodCallHandler(methodChannelPlugin);
        return methodChannelPlugin;
    }

    private MethodChannelPlugin(Activity activity, MethodChannel channel) {
        this.activity = activity;
        this.channel = channel;

    }
    //调用flutter端方法,无返回值
    public void invokeMethod(String method, Object o) {
        channel.invokeMethod(method, o);
    }
    //调用flutter端方法,有返回值
    public void invokeMethod(String method, Object o, MethodChannel.Result result) {
        channel.invokeMethod(method, o, result);
    }

    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
        switch (methodCall.method) {
            case "send"Result.success (// Return method name // return value to flutter end"MethodChannelPlugin received:" + methodCall.arguments);
                Toast.makeText(activity, methodCall.arguments + "", Toast.LENGTH_SHORT).show();
                if (activity instanceof FlutterAppActivity) {
                    ((FlutterAppActivity) activity).showContent(methodCall.arguments);
                }
                break;
            default:
                result.notImplemented();
                break; }}}Copy the code

The author of the Android side of the code to do a simple package, or very good understanding. Here is the flutter code implementation.

class _MyHomePageState extends State<MyHomePage> {
  MethodChannel _methodChannel = MethodChannel("MethodChannelPlugin");
  @override
  void initState() {
    _methodChannel.setMethodCallHandler((handler) => Future<String>(() {
          print("_methodChannel:${handler}"); Switch (handler.method) {// Listen for native method names and arguments.case "send": _send(handler.arguments); // Handler. arguments indicate method arguments passed by nativebreak; }})); super.initState(); } // Native calls the flutter method void _send(arg) {setState(() {
      _content = arg;
    });
  }
  String _resultContent = ""; //flutter calls the corresponding method void of native_sendToNative() {
      Future<String> future =
          _methodChannel.invokeMethod("send", _controller.text);
      future.then((message) {
        setState(() {//message is the data returned by native _resultContent ="Return value:" + message;
        });
      });
  }

  @override
  Widget build(BuildContext context) {...}
}
Copy the code

This is the code implementation for communicating with MethodChannel. It’s relatively simple. On the Android side, you just need to call the invokeMethod method of the MethodChannelPlugin. To use the flutter side just refer to the _sendToNative implementation.

3.4, BasicMessageChannel

BasicMessageChannel is a communication method that can send messages to each other between native and flutter. It supports the most data types and has the widest range of applications. EventChannel and MethodChannel scenarios can be implemented using BasicMessageChannel, However, the application scenario of BasicMessageChannel is not necessarily implemented using EventChannel and MethodChannel. This method has a return value. It can be used as follows.

Let’s start with the implementation of the Android code.

// The supported data type is String. // The supported data type is String. public class BasicMessageChannelPlugin implements BasicMessageChannel.MessageHandler<String> { private Activity activity; private BasicMessageChannel<String> messageChannel; static BasicMessageChannelPlugin registerWith(FlutterView flutterView) {return new BasicMessageChannelPlugin(flutterView);
    }

    private BasicMessageChannelPlugin(FlutterView flutterView) {
        this.activity = (Activity) flutterView.getContext();
        this.messageChannel = new BasicMessageChannel<String>(flutterView, "BasicMessageChannelPlugin", StringCodec.INSTANCE);
        messageChannel.setMessageHandler(this);
    }


    @Override
    public void onMessage(String s, BasicMessageChannel.Reply<String> reply) {
        reply.reply("BasicMessageChannelPlugin received." + s);
        if(activity instanceof FlutterAppActivity) { ((FlutterAppActivity) activity).showContent(s); } } void send(String str, BasicMessageChannel.Reply<String> reply) { messageChannel.send(str, reply); }}Copy the code

The author of the Android side of the code to do a simple package, or very good understanding. Here is the flutter code implementation.

Class _MyHomePageState extends State<MyHomePage> {//StringCodec() is an encoding format BasicMessageChannel<String> _basicMessageChannel  = BasicMessageChannel("BasicMessageChannelPlugin", StringCodec());


  @override
  void initState() {
    _basicMessageChannel.setMessageHandler((message) => Future<String>(() {
          print(message); // Message passes data to nativesetState(() { _content = message; }); // Return the value to Androidreturn "Received a Native message:"+ message; })); _controller = TextEditingController(); super.initState(); } // Send a message to native void_sendToNative() {
      Future<String> future = _basicMessageChannel.send(_controller.text);
      future.then((message) {
        _resultContent = "Return value:" + message;
      });
  }

  @override
  Widget build(BuildContext context) {...}
}
Copy the code

Above is the code implementation using BasicMessageChannel to communicate. Just call on the Android end BasicMessageChannelPlugin the send method can send data to the flutter, BasicMessageChannel. The Reply is the return value of the callback methods. To use the flutter side just refer to the _sendToNative implementation.

4. Communication principle

According to the source code of Android and Flutter communication, the implementation of Flutter is relatively simple. All of them use ByteBuffer as data carrier, and then send and receive data through BinaryMessenger. The overall design is as follows.

As you can see from the figure, the Android side uses the same design as the Flutter side. We said that communication happens asynchronously, so where is the thread switch? It’s actually implemented at the bottom of the system. In the communication between Android and Flutter, the underlying system shields a large number of complex operations, such as thread switching and data copying. This enables easy communication between the Android side and the Flutter side.

On the Android side, BinaryMessenger is an interface, which is implemented in FlutterView. In BinaryMessenger methods, JNI is used to communicate with the bottom layer of the system. On the side of Flutter, BinaryMessenger is a class. The purpose of this class is to communicate with the window class, which actually communicates with the underlying system.

5, summary

In the hybrid development mode of Android and Flutter, there will be no lack of communication scenarios between them. Understanding the various ways and uses of Android and Flutter communication will help you choose a proper way to implement Flutter.

The last

If you see this and you think it’s good, give it a thumbs up? If you think there is something worth improving, please leave a message. Will inquire seriously, correct inadequacy. thank you

The reason why someone is always better than you is because they are already better and are constantly trying to become better, while you are still satisfied with the status quo and secretly happy! I hope you can give me a thumbs-up and follow me, and I will update the technical dry goods in the future. Thank you for your support!

Forward share + attention, get more information every day

The road to being an Android architect is a long one.