This article is first published in the wechat public account “Android development journey”, welcome to follow, get more technical dry goods
Wan-android Jetpack VersionAndroid Jetpack architecture for componentized application developmentWelcome to star
Project address of Flutter wan-AndroidFlutter Wan – AndroidWelcome to star
Communication scenarios
When we do Flutter hybrid development, we usually need to carry out communication between Flutter and Native. For example, Dart calls Native’s album to select images, and Native actively transmits power and GPS information to Dart, etc. Communication in mixed development usually has the following types:
- Native passes data to Dart when a Flutter is initialized.
- Native sends data to Dart.
- Dart sends data to Native.
- Dart sends data to Native, and Native sends data back to Dart.
The first method of communication was described in our discussion of native project access to Flutter. Those interested in this method can move on to the detailed guide to Flutter hybrid Development (I) : Android Project Integration with the Flutter module.
Communication mechanism
The communication mechanism between the Flutter and Native terminal is implemented through the Platform Channel. Messages are transmitted between the Flutter and Native ends using channels. The details are shown in the figure below:
As can be seen from the figure, the communication between the two ends is bidirectional and asynchronous. Flutter defines three different types of channels:
- BasicMessageChannel: Used to send string or semi-structured messages, continuously communicate, and respond to messages received.
- MethodChannel: Used to pass method calls, one-time communication. Dart is typically used to call Native methods.
- EventChannel: Used for data flow communication. The communication is continuous. After receiving a message, the message cannot be replied. Typically used for Native to Dart communication.
Let’s take a look at the specific use and introduction of these three Channel communication modes.
BasicMessageChannel
Related methods on Android:
BasicMessageChannel(BinaryMessenger messenger, String name, MessageCodec<T> codec)
Copy the code
- The Messenger parameter is the FlutterView, which is a tool for sending and receiving messages.
- The name parameter is the name and unique identifier of a channel, which must be consistent with the Dart end.
- Codec is a message codec that also needs to be unified with the Dart side. It encrypts the message as it is sent, and the DART side decrypts the message as it receives it, delivering binary data. It has four types: BinaryCodec, StringCodec, JSONMessageCodec and StandardMessageCodec, all of which belong to the category of MessageCodec. Default is StandardMessage Dec if not specified.
We use the setMessageHandler method when we need to receive messages from the Dart side:
void setMessageHandler(BasicMessageChannel.MessageHandler<T> handler)
Copy the code
The handler parameter is the message handler that works with BinaryMessenger to complete the processing of the message. It is an interface implemented in the onMessage method:
public interface MessageHandler<T> {
void onMessage(T message, BasicMessageChannel.Reply<T> reply);
}
Copy the code
The message argument is the data sent to Dart, and reply is the callback function used to reply to the message, providing reply.reply(“”) to set the content of the reply.
Dart sends a Dart message to Native. Dart sends a Dart message to Native. Dart sends a Dart message to Native.
void send(T message)
void send(T message, BasicMessageChannel.Reply<T> callback)
Copy the code
The message argument is the data to be sent to Dart, and the callback is used to receive the Dart response.
Dart side correlation method:
const BasicMessageChannel(this.name, this.codec);
Copy the code
Codec is the message codec, and the two parameters must be consistent at both ends.
Dart to receive messages from Native, setMessageHandler is set:
void setMessageHandler(Future<T> handler(T message))
Copy the code
Handler is the message handler that works with BinaryMessenger to process the message.
Send a message to the Native end using send:
Future<T> send(T message)
Copy the code
The parameter message is the parameter to be passed. Future is a callback function that waits for a reply from Native after sending a message.
The Android terminal and the Flutter terminal send messages to each other and return messages to each other when the message is received
Android code:
BasicMessageChannel<String> BasicMessageChannel = new BasicMessageChannel<>(flutterView,"BasicMessageChannelPlugin",StringCodec.INSTANCE); / / to accept news basicMessageChannel. SetMessageHandler ((message, reply) - > {mTvDart. SetText (message); reply.reply("Dart data received: Accepted successfully"); }); Basicmessagechannel. send(message, reply -> mtvdart.settext (reply));Copy the code
Dart side code:
Static const BasicMessageChannel<String> _basicMessageChannel = BasicMessageChannel("BasicMessageChannelPlugin", StringCodec()); // Accept the message voidhandleBasicMessageChannel() {
_basicMessageChannel
.setMessageHandler((String message) => Future<String>(() {
setState(() {
showMessage = message;
});
return "Received a message from Native: Acceptance successful"; })); } response = await _basicMessageChannel. Send (_controller.text);setState(() {
showMessage = response;
});
Copy the code
The final result is shown in the figure below. The upper part of the red dividing line is divided into Native pages and the lower part into Flutter pages.
MethodChannel
The parameter types and meanings of the related methods using MethodChannel are the same as those of BasicMessageChannel, which are not explained in detail below.
Androd:
MethodChannel(BinaryMessenger messenger, String name)
MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec)
Copy the code
The first constructor will construct a StandardMethodCodec MethodCodec INSTANCE type. MethodCodec defines two types: JSONMethodCodec and StandardMethodCodec.
If you want to receive messages from the Dart side, use:
setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler)
Copy the code
MethodCallHandler is the interface, and the callback method is:
public interface MethodCallHandler {
void onMethodCall(MethodCall call, MethodChannel.Result result);
}
Copy the code
The call argument has two member variables, call.method (String) representing the name of the calling method, and call.arguments (Object) representing the input arguments passed by the calling method. Result is the callback that responds to this message providing result.success, result.error, result.notImplemented method calls.
Sending a message to actively invoke the Dart code uses the invokeMethod method
invokeMethod(@NonNull String method, @Nullable Object arguments)
invokeMethod(String method, @Nullable Object arguments, Result callback)
Copy the code
The second method has an additional callback, which is used to receive the Dart side’s response to the message.
public interface Result {
void success(@Nullable Object result);
void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails);
void notImplemented();
}
Copy the code
Dart side correlation method:
const MethodChannel(this.name, [this.codec = const StandardMethodCodec()])
Copy the code
The constructor defaults to using the StandardMethodCodec codec.
The setMethodCallHandler method receives method calls from Native, and the invokeMethod method calls Native.
void setMethodCallHandler(Future<dynamic> handler(MethodCall call))
Copy the code
Future<T> invokeMethod<T>(String method, [ dynamic arguments ])
Copy the code
MethodChannel: The Native end calls the getPlatform method on the Dart end to return to the current OS platform. The Dart end calls the getBatteryLevel method on the Native end to obtain the current battery quantity.
Android code:
MethodChannel = new MethodChannel(flutterView,"MethodChannelPlugin");
mBtnTitle.setOnClickListener(new View.OnClickListener() {@ Override public void onClick (View v) {/ / call the dart end getPlatform method methodChannel. InvokeMethod ("getPlatform", null, new MethodChannel.Result() {
@Override
public void success(@Nullable Object result) {
mTvDart.setText(result.toString());
}
@Override
public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
mTvDart.setText(errorCode + "= =" + errorMessage);
}
@Override
public void notImplemented() {
mTvDart.setText("Not implemented getPlatform method"); }}); }}); / / accept the dart call methodChannel. SetMethodCallHandler ((call, result) - > {switch (call) method) {case "getBatteryLevel":
int batteryLevel = getBatteryLevel();
if(batteryLevel ! = -1) { result.success("Electric quantity is:" + batteryLevel);
} else {
result.error("1001"."Call error", null);
}
break;
default:
result.notImplemented();
break; }});Copy the code
Dart side code:
// receive
void handleMethodChannelReceive() {
Future<dynamic> platformCallHandler(MethodCall call) async {
switch (call.method) {
case "getPlatform":
returngetPlatformName(); // Call success method //return PlatformException(code: '1002',message: "Abnormal"); / / call the errorbreak; } } _methodChannel.setMethodCallHandler(platformCallHandler); // _methodChannel.setMethodCallHandler(null); / / call notImplemented} / / send void handleMethodChannelSend () async {try {response = await _methodChannel. InvokeMethod ("getBatteryLevel");
print(response);
setState(() { showMessage = response; }); } catch (e) {// Catch error and notImplemented exceptionssetState(() { showMessage = e.message; }); }}Copy the code
When we use setMethodCallHandler to receive a message to Native, we call the relevant method directly to invoke the Success callback on the Native side.
If an exception such as PlatformException is thrown directly, then the error callback on the Native side is called.
PlatformException(code: '1002',message: "Abnormal")
Copy the code
If we just set handler to NULL
_methodChannel.setMethodCallHandler(null);
Copy the code
The notImplemented method callback is called on the Native side.
Similarly, when we use the invokeMethod method on the Dart side, we need to carry out exception capture so that we can receive the error and notImplemented method callbacks from the Native side.
The final result is shown in the figure below. The upper part of the red dividing line is divided into Native pages and the lower part into Flutter pages.
EventChannel
The internal implementation of EventChannel is actually done through MethodChannel.
Android terminal related code:
EventChannel(BinaryMessenger messenger, String name)
EventChannel(BinaryMessenger messenger, String name, MethodCodec codec)
Copy the code
Again, there are two constructs, the default codec is StandardMethodCodec, and the codec for EventChannel and MethodChannel both fall into the MethodCodec category.
Use setStreamHandler to listen for messages sent from the Dart side,
void setStreamHandler(EventChannel.StreamHandler handler)
Copy the code
Where handler is an interface:
public interface StreamHandler {
void onListen(Object args, EventChannel.EventSink eventSink);
void onCancel(Object o);
}
Copy the code
Args is the parameter for the DART side to initialize the listening stream, and eventSink sets three callbacks, namely success, Error and endofStream. They correspond to onData, Error, and onDone callbacks on the Dart side.
Dart side related code:
const EventChannel(this.name, [this.codec = const StandardMethodCodec()]);
Copy the code
Initialize a channel object with EventChannel. If receiving data from Native requires defining a broadcast stream:
Stream<dynamic> receiveBroadcastStream([ dynamic arguments ])
Copy the code
Registration is done by calling the Stream’s Listen method.
EventChannel: The Native end actively sends the battery information to the Dart end. The Dart end displays the information after receiving it.
Android code:
EventChannel eventChannel = new EventChannel(flutterView, "EventChannelPlugin");
eventChannel.setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
events.success(arguments.toString() + getBatteryLevel());
//events.error("111"."Error occurred"."");
//events.endOfStream();
}
@Override
public void onCancel(Object arguments) {
}
});
Copy the code
Dart side code:
//init
static const EventChannel _eventChannel = EventChannel("EventChannelPlugin");
//receive
void handleEventChannelReceive() {streamSubscription = _eventChannel.receiveStream () {streamSubscription = _onChannel.receiveStream () {streamSubscription = _onData, onError: _onError, onDone: _onDone); } void_onDone() {
setState(() {
showMessage = "endOfStream";
});
}
_onError(error) {
setState(() {
PlatformException platformException = error;
showMessage = platformException.message;
});
}
void _onData(message) {
setState(() {
showMessage = message;
});
}
Copy the code
The Event. Success method is used to send information, and the DART side listens for information on the Stream. When events.error is called at the Native end, error needs to be converted to PlatformException in the onError callback at the Dart end to obtain the information related to the exception.
The final result is shown in the figure below. The upper part of the red dividing line is divided into Native pages and the lower part into Flutter pages.
conclusion
The three communication modes between Android and Dart are explained. The composition and application of the method are analyzed in detail. Each method corresponds to a different use scenario, you can choose according to your needs, practice makes perfect.
The article is posted some code snippets, all Demo source code has been uploaded to the background, pay attention to the public number reply “mixed development” can get the download link.
Scan the qr code below to follow the public account for more technical dry goods.
Recommended reading
Dart Quick Start
A detailed guide to the Integration of the Flutter module into the Android project
A detailed guide to iOS project integration with the Flutter module