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
name
The logical channel on which communication happenscodec
: MessageCodec, The message codec used by this channelbinaryMessenger
: 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