Flutter uses a flexible system that allows developers to call platform-specific apis, whether in Java or Kotlin code on Android or in ObjectiveC or Swift code on iOS.
The Flutter platform-specific API support does not rely on code generation, but on a flexible way of messaging:
- The Flutter part of the application sends messages to its app host (iOS or Android) via platform channels.
- The platform channel on which the host listens and receives the message. It then invokes the platform-specific API (using the native programming language) — and sends the response back to the client, the Flutter part of the application.
Get your phone’s battery power from Flutter on Android.
1.0 Obtaining battery power
First look at the code of the Flutter layer, need to take a Channel only one name in the whole application, such as samples. The Flutter. IO/’.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; . class _MyHomePageState extends State<MyHomePage> { static const platform = const MethodChannel('samples.flutter.io/battery');
String _batteryLevel = 'Unknown battery level.';
// Get battery level.
}
Copy the code
The method on the channel is then called with a string identifying the called method, such as getBatteryLevel, which may also fail and can be wrapped with a try-catch.
The returned result updates the user interface state with setState,_batteryLevel, where await will return the Future and will be assigned to Result only after receiving the battery result returned by the Android platform. Then the code goes down and assigns batteryLevel and updates the state.
Future<Null> _getBatteryLevel() async {
String batteryLevel;
try {
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
setState(() {
_batteryLevel = batteryLevel;
});
}
Copy the code
Finally, create a button in Build to refresh the value and a Text to display the power value.
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done// by the _incrementCounter method above. // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets.return Material(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new RaisedButton(
child: Text('Get Battery Level'),
onPressed: _getBatteryLevel,
),
Text(_batteryLevel),
],
),
),
);
}
Copy the code
This is the code for the Flutter layer. Let’s take an example of how to implement the Platform layer on Android. In MainActivity, we need to define a channel name that is the same as the Flutter layer and return the Result to the Flutter as Result:
private static final String CHANNEL = "samples.flutter.io/battery";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler((call, result) -> {
if (call.method.equals("getBatteryLevel")) {
int batteryLevel = getBatteryLevel();
if(batteryLevel ! = -1) { result.success(batteryLevel); }else {
result.error("UNAVAILABLE"."Battery level not available.", null); }}else{ result.notImplemented(); }}); GeneratedPluginRegistrant.registerWith(this); }Copy the code
Next add Java code to get the battery power using the Android battery API:
private int getBatteryLevel() {
int batterylevel = -1;
if (VERSION.SDK_INT > VERSION_CODES.LOLLIPOP) {
BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
batterylevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
} else {
Intent intent = new ContextWrapper(getApplicationContext()).registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
batterylevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100)
/ intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
}
return batterylevel;
}
Copy the code
Take a look at the rendering:
Next, let’s look at the principle of Flutter and Native communication, mainly the principle of the upper Flutter active emission.
2.0 Flutter
Layer principle
Take a look at one of the official communications:
2.1 MethodChannel
The entry is MethodChannel, so let’s start with this code:
### platform_channel.dart
class MethodChannel {
const MethodChannel(this.name, [this.codec = const StandardMethodCodec()]);
/// The logical channel on whichcommunication happens, not null. final String name; /// The message codec used by this channel, not null. final MethodCodec codec; @optionalTypeArgs Future<T> invokeMethod<T>(String method, [dynamic arguments]) async { assert(method ! = null); final ByteData result = await BinaryMessages.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
The constructor requires a name and optional MethodCodec, which is the identity of the MethodChannel, and MethodCodec, which is the codec that determines what type of data we can pass. Take a look at the data provided on the official website
MethodCodec is optional, default is StandardMethodCodec, which is used when you initiate a method call invokeMethod. StandardMethodCodec serializes the method names and parameters into binary, receives the result returned by the platform as binary data, and deserializes it into the Dart data type.
2.2 invokeMethod
In addition, invokeMethod is an asynchronous method that returns a Future
type and must be await when receiving the return value of this method. To call with await, you must run in a function with the async flag.
Then send the binarymessages.send message
2.3 BinaryMessages.send
### platform_messages.dart
static 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
Send is used to send binary messages to the channel corresponding to the platform plug-in.
So _mockHandlers is used for debugging, so if you look at the data structure, if you set up a debugging Handler it just returns, it doesn’t talk to the platform.
### platform_messages.dart
// Mock handlers that intercept and respond to outgoing messages.
static final Map<String, _MessageHandler> _mockHandlers =
<String, _MessageHandler>{};
static void setMockMessageHandler(String channel, Future<ByteData> handler(ByteData message)) {
if (handler == null)
_mockHandlers.remove(channel);
else
_mockHandlers[channel] = handler;
}
Copy the code
Now let’s look at the call to _sendPlatformMessage in send
2.4 _sendPlatformMessage
### platform_messages.dart
static 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: 'during a platform message response callback')); }});return completer.future;
}
Copy the code
The Completer above is an object that can be used to control the Future, because normal futures are actually created and executed or queued up for execution, and the user can only passively receive the Future’s callback. Completer can execute the Future with completer.plete or Completer. Error.
SendPlatformMessage is called under the window
2.5 sendPlatformMessage
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) {
final String error =
_sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
if(error ! = null) throw new Exception(error); } String _sendPlatformMessage(String name, PlatformMessageResponseCallback callback, ByteData data) native'Window_sendPlatformMessage';
Copy the code
There is a native method called, so we need to find this native method. You need to download the Engine layer code at: Engine.
3.0 Engine
Layer principle
3.1 _SendPlatformMessage
Use the identifier Window_sendPlatformMessage above to go to the Engine layer to find the registration method:
### window.cc
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}}); } void _SendPlatformMessage(Dart_NativeArguments args) { tonic::DartCallStatic(&SendPlatformMessage, args); }Copy the code
It calls the SendPlatformMessage method.
3.2 window
—SendPlatformMessage
Platform messages can only be sent from the main ISOLATE. Call to MethodChannel can only be sent from the main ISOLATE.
### window.cc
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
Then the main line of code eventually calls the virtual method HandlePlatformMessage in The WindowClient
dart_state->window()->client()->HandlePlatformMessage
Copy the code
3.3 window
—WindowClient
Take a look at the client definition:
### window.h
class Window final {
public:
explicit Window(WindowClient* client);
~Window();
WindowClient* client() const { returnclient_; }... } class WindowClient { public: virtual std::string DefaultRouteName() = 0; virtual void ScheduleFrame() = 0; virtual void Render(Scene* scene) = 0; virtual void UpdateSemantics(SemanticsUpdate* update) = 0; virtual void HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) = 0; virtual FontCollection& GetFontCollection() = 0; virtual void UpdateIsolateDescription(const std::string isolate_name, int64_t isolate_port) = 0; protected: virtual ~WindowClient(); };Copy the code
If you run into a pure virtual function, you have to find a class that inherits from Windows Client.
3.4 RuntimeController
Find only one place to inherit:
class RuntimeController final : public WindowClient
Copy the code
Inside this class:
### RuntimeController.cc
void RuntimeController::HandlePlatformMessage(
fml::RefPtr<PlatformMessage> message) {
client_.HandlePlatformMessage(std::move(message));
}
Copy the code
Finally, it is called through client_, which looks like proxy mode. Then look for client_ and see in the constructor:
### RuntimeController.cc
RuntimeController::RuntimeController(
RuntimeDelegate& p_client,
DartVM* p_vm,
fml::RefPtr<const DartSnapshot> p_isolate_snapshot,
fml::RefPtr<const DartSnapshot> p_shared_snapshot,
TaskRunners p_task_runners,
fml::WeakPtr<SnapshotDelegate> p_snapshot_delegate,
fml::WeakPtr<IOManager> p_io_manager,
std::string p_advisory_script_uri,
std::string p_advisory_script_entrypoint,
std::function<void(int64_t)> idle_notification_callback,
WindowData p_window_data)
: client_(p_client),
vm_(p_vm),
isolate_snapshot_(std::move(p_isolate_snapshot)),
shared_snapshot_(std::move(p_shared_snapshot)),
task_runners_(p_task_runners),
snapshot_delegate_(p_snapshot_delegate),
io_manager_(p_io_manager),
advisory_script_uri_(p_advisory_script_uri),
advisory_script_entrypoint_(p_advisory_script_entrypoint),
idle_notification_callback_(idle_notification_callback),
window_data_(std::move(p_window_data)),
root_isolate_(
DartIsolate::CreateRootIsolate(vm_->GetVMData()->GetSettings(),
isolate_snapshot_,
shared_snapshot_,
task_runners_,
std::make_unique<Window>(this),
snapshot_delegate_,
io_manager_,
p_advisory_script_uri,
p_advisory_script_entrypoint)) {
std::shared_ptr<DartIsolate> root_isolate = root_isolate_.lock();
root_isolate->SetReturnCodeCallback([this](uint32_t code) {
root_isolate_return_code_ = {true, code};
});
Copy the code
As you can see, with client_ which is actually RuntimeDelegate,
3.5 RuntimeDelegate
So let’s move on to the RuntimeDelegate, and then we run into pure virtual functions.
### runtime_delegate.h
namespace blink {
class RuntimeDelegate {
public:
virtual std::string DefaultRouteName() = 0;
virtual void ScheduleFrame(bool regenerate_layer_tree = true) = 0;
virtual void Render(std::unique_ptr<flow::LayerTree> layer_tree) = 0;
virtual void UpdateSemantics(
blink::SemanticsNodeUpdates update,
blink::CustomAccessibilityActionUpdates actions) = 0;
virtual void HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) = 0;
virtual FontCollection& GetFontCollection() = 0;
virtual void UpdateIsolateDescription(const std::string isolate_name,
int64_t isolate_port) = 0;
protected:
virtual ~RuntimeDelegate();
};
} // namespace blink
Copy the code
It’s just a matter of seeing which class inherits RuntimeDelegate, and only one class, Engine, inherits.
3.6 Engine
### engine.h
class Engine final : public blink::RuntimeDelegate
Copy the code
See the code in engine.cc:
### engine.cc
void Engine::HandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message) {
if (message->channel() == kAssetChannel) {
HandleAssetPlatformMessage(std::move(message));
} else{ delegate_.OnEngineHandlePlatformMessage(std::move(message)); }}Copy the code
Engine lazy delegate_, see Engine constructor:
### engine.cc
Engine::Engine(Delegate& delegate,
blink::DartVM& vm,
fml::RefPtr<const blink::DartSnapshot> isolate_snapshot,
fml::RefPtr<const blink::DartSnapshot> shared_snapshot,
blink::TaskRunners task_runners,
blink::Settings settings,
std::unique_ptr<Animator> animator,
fml::WeakPtr<blink::SnapshotDelegate> snapshot_delegate,
fml::WeakPtr<blink::IOManager> io_manager)
: delegate_(delegate),
settings_(std::move(settings)),
animator_(std::move(animator)),
activity_running_(false),
have_surface_(false),
weak_factory_(this) {
// Runtime controller is initialized here because it takes a reference to this
// object as its delegate. The delegate may be called in the constructor and
// we want to be fully initilazed by that point.
runtime_controller_ = std::make_unique<blink::RuntimeController>(
*this, // runtime delegate
&vm, // VM
std::move(isolate_snapshot), // isolate snapshot
std::move(shared_snapshot), // shared snapshot
std::move(task_runners), // task runners
std::move(snapshot_delegate), // snapshot delegate
std::move(io_manager), // io manager
settings_.advisory_script_uri, // advisory script uri
settings_.advisory_script_entrypoint, // advisory script entrypoint
settings_.idle_notification_callback // idle notification callback
);
}
Copy the code
The Delegate is defined in engine.h:
### engine.h
class Delegate {
public:
virtual void OnEngineUpdateSemantics(
blink::SemanticsNodeUpdates update,
blink::CustomAccessibilityActionUpdates actions) = 0;
virtual void OnEngineHandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message) = 0;
virtual void OnPreEngineRestart() = 0;
virtual void UpdateIsolateDescription(const std::string isolate_name,
int64_t isolate_port) = 0;
};
Copy the code
Is familiar with pure virtual functions OnEngineHandlePlatformMessage, back to the Engine, and see which side is constructed this object can find delegate_, finally find the shell.
3.7 Shell
In the shell, there is a field attribute called Engine that is constructed by passing the shell into it, so the top delegate_ is the shell
### shell.cc
std::unique_ptr<Engine> engine;
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetUITaskRunner(),
fml::MakeCopyable([&ui_latch, //
&engine, //
shell = shell.get(), //
isolate_snapshot = std::move(isolate_snapshot), //
shared_snapshot = std::move(shared_snapshot), //
vsync_waiter = std::move(vsync_waiter), //
snapshot_delegate = std::move(snapshot_delegate), //
io_manager = io_manager->GetWeakPtr() //
]() mutable {
TRACE_EVENT0("flutter"."ShellSetupUISubsystem");
const auto& task_runners = shell->GetTaskRunners();
// The animator is owned by the UI thread but it gets its vsync pulses
// from the platform.
auto animator = std::make_unique<Animator>(*shell, task_runners,
std::move(vsync_waiter));
engine = std::make_unique<Engine>(*shell, //
*shell->GetDartVM(), //
std::move(isolate_snapshot), //
std::move(shared_snapshot), //
task_runners, //
shell->GetSettings(), //
std::move(animator), //
std::move(snapshot_delegate), //
std::move(io_manager) //
);
ui_latch.Signal();
}));
Copy the code
Found in the shell OnEngineHandlePlatformMessage method:
### shell.cc
// |shell::Engine::Delegate|
void Shell::OnEngineHandlePlatformMessage(
fml::RefPtr<blink::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
So let’s look at the first if judgment, if it equals kSkiaChannel it’s going to return, so let’s look at the value of kSkiaChannel
constexpr char kSkiaChannel[] = "flutter/skia";
Copy the code
It is obvious that our custom channel will not go into the judgment logic, which is to throw a task to PlatformTaskRunner. The detailed description of thread can be seen in the last summary, because it is long, in order to call the coherence of the logic.
3.8 PlatformView
— PlatformViewAndroid
PlatformView, but it’s a virtual function:
### platform_view.h
virtual void HandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message);
Copy the code
Then find its descendant class PlatformViewAndroid
### platform_view_android.cc// |shell::PlatformView| void PlatformViewAndroid::HandlePlatformMessage( fml::RefPtr<blink::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 inInvokePlatformMessageXxxResponseCallback. FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(), nullptr, response_id); }}Copy the code
You can already see a taste of Java in this method:
- First, record the ID of a callback
response_id
, will put the callback intopending_responses_
In the
std::unordered_map<int, fml::RefPtr<blink::PlatformMessageResponse>>
pending_responses_;
Copy the code
-
Call StringToJavaString below JNI to convert Channel name to Java string;
-
If the call takes parameters, jni:: env->NewByteArray is called to turn into a JNI array
-
The last call FlutterViewHandlePlatformMessage
This function is defined in platform_view_android_jni.cc
3.9 FlutterViewHandlePlatformMessage
You can see that the JNI env has started calling the JNI function table registered in the virtual machine.
### platform_view_android_jni.cc
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
The key is to find the corresponding method for the g_handle_platform_message_method symbol table,
### platform_view_android_jni.cc
g_handle_platform_message_method =
env->GetMethodID(g_flutter_jni_class->obj(), "handlePlatformMessage"."(Ljava/lang/String; [BI)V");
Copy the code
Those of you who have done JNI know that handlePlatformMessage is the Android equivalent.
3.10 FlutterJNI.java
Finally came to the Java layer, suddenly feel Java so friendly.
### FlutterJNI.java
// Called by native.
@SuppressWarnings("unused")
private void handlePlatformMessage(final String channel, byte[] message, final int replyId) {
if(platformMessageHandler ! = null) { platformMessageHandler.handleMessageFromDart(channel, message, replyId); } // TODO(mattcarroll):log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391)
}
Copy the code
Next, let’s start with the code written on the Android layer
4.0 Java
Layer principle
4.1 MethodChannel.java
Going back to the first verse,
### MainActivity.java
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler((call, result) -> {})
Copy the code
### MethodChannel.java
private final BinaryMessenger messenger;
public MethodChannel(BinaryMessenger messenger, String name) {
this(messenger, name, StandardMethodCodec.INSTANCE);
}
public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
this.messenger.setMessageHandler(this.name, handler == null ? null : new MethodChannel.IncomingMethodCallHandler(handler));
}
Copy the code
4.2 MethodChannel.java
BinaryMessenger is an interface that implements multiple classes. In our constructor we pass in FlutterView,
It will eventually be called under the FlutterNativeView
### FlutterNativeView.java
private final Map<String, BinaryMessageHandler> mMessageHandlers;
public void setMessageHandler(String channel, BinaryMessageHandler handler) {
if (handler == null) {
this.mMessageHandlers.remove(channel);
} else{ this.mMessageHandlers.put(channel, handler); }}Copy the code
Is the platformMessageHandler under FlutterJNI related to the FlutterNativeView?
Look at the constructor of the FlutterNativeView:
### FlutterNativeView.java
public FlutterNativeView(Context context, boolean isBackgroundView) {
this.mNextReplyId = 1;
this.mPendingReplies = new HashMap();
this.mContext = context;
this.mPluginRegistry = new FlutterPluginRegistry(this, context);
this.mFlutterJNI = new FlutterJNI();
this.mFlutterJNI.setRenderSurface(new FlutterNativeView.RenderSurfaceImpl());
this.mFlutterJNI.setPlatformMessageHandler(new FlutterNativeView.PlatformMessageHandlerImpl());
this.mFlutterJNI.addEngineLifecycleListener(new FlutterNativeView.EngineLifecycleListenerImpl());
this.attach(this, isBackgroundView);
this.assertAttached();
this.mMessageHandlers = new HashMap();
}
Copy the code
Can see FlutterNativeView holds FlutterJNI PlatformMessageHandlerImpl is also its inner class:
### FlutterNativeView.java
private final class PlatformMessageHandlerImpl implements PlatformMessageHandler {
private PlatformMessageHandlerImpl() {
}
public void handleMessageFromDart(final String channel, byte[] message, final int replyId) {
FlutterNativeView.this.assertAttached();
BinaryMessageHandler handler = (BinaryMessageHandler)FlutterNativeView.this.mMessageHandlers.get(channel);
if(handler ! = null) { try { ByteBuffer buffer = message == null ? null : ByteBuffer.wrap(message); handler.onMessage(buffer, newBinaryReply() {
private final AtomicBoolean done = new AtomicBoolean(false);
public void reply(ByteBuffer reply) {
if(! FlutterNativeView.this.isAttached()) { Log.d("FlutterNativeView"."handleMessageFromDart replying ot a detached view, channel=" + channel);
} else if (this.done.getAndSet(true)) {
throw new IllegalStateException("Reply already submitted");
} else {
if (reply == null) {
FlutterNativeView.this.mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
} else{ FlutterNativeView.this.mFlutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position()); }}}}); } catch (Exception var6) { Log.e("FlutterNativeView"."Uncaught exception in binary message listener", var6); FlutterNativeView.this.mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId); }}else{ FlutterNativeView.this.mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId); }}... }Copy the code
- The previously registered ones are first fetched from mMessageHandlers
MethodChannel
And then callonMessage
How? Some of you found out that we passedsetMethodCallHandler
Set up theMethodCallHandler
, there is noonMessage
methods
public interface MethodCallHandler {
void onMethodCall(MethodCall var1, MethodChannel.Result var2);
}
Copy the code
Actually there is a detail didn’t say, pass in MethodCallHandler will construct into IncomingMethodCallHandler
### MethodChannel.java
IncomingMethodCallHandler(MethodChannel.MethodCallHandler handler) {
this.handler = handler;
}
public void onMessage(ByteBuffer message, final BinaryReply reply) {
MethodCall call = MethodChannel.this.codec.decodeMethodCall(message);
try {
this.handler.onMethodCall(call, new MethodChannel.Result() { public void success(Object result) { reply.reply(MethodChannel.this.codec.encodeSuccessEnvelope(result)); } public void error(String errorCode, String errorMessage, Object errorDetails) { reply.reply(MethodChannel.this.codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails)); } public voidnotImplemented() { reply.reply((ByteBuffer)null); }}); } catch (RuntimeException var5) { Log.e("MethodChannel#" + MethodChannel.this.name, "Failed to handle method call", var5);
reply.reply(MethodChannel.this.codec.encodeErrorEnvelope("error", var5.getMessage(), (Object)null)); }}Copy the code
- And then it goes through
invokePlatformMessageResponseCallback
The callback back
FlutterNativeView.this.mFlutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position());
Copy the code
5.0 the callback
The callback can be found in the Engine layer using replyId above, which is essentially a callback back along the reverse path above.
5.1 FlutterJNI.java
@UiThread
public void invokePlatformMessageResponseCallback(int responseId, ByteBuffer message, int position) {
this.ensureAttachedToNative();
this.nativeInvokePlatformMessageResponseCallback(this.nativePlatformViewId, responseId, message, position);
}
private native void nativeInvokePlatformMessageResponseCallback(long var1, int var3, ByteBuffer var4, int var5);
Copy the code
The Engine layer is called back through native JNI
5.2 platform_view_android_jni.cc
### platform_view_android_jni.cc
static void InvokePlatformMessageResponseCallback(JNIEnv* env,
jobject jcaller,
jlong shell_holder,
jint responseId,
jobject message,
jint position) {
ANDROID_SHELL_HOLDER->GetPlatformView()
->InvokePlatformMessageResponseCallback(env, //
responseId, //
message, //
position //
);
}
Copy the code
5.3 platform_view_android.cc
The main thing here is to find the callback that was previously registered in HandlePlatformMessage and call it:
### platform_view_android.cc
void PlatformViewAndroid::InvokePlatformMessageResponseCallback(
JNIEnv* env,
jint response_id,
jobject java_response_data,
jint java_response_position) {
if(! response_id)return;
auto it = pending_responses_.find(response_id);
if (it == pending_responses_.end())
return;
uint8_t* response_data =
static_cast<uint8_t*>(env->GetDirectBufferAddress(java_response_data));
std::vector<uint8_t> response = std::vector<uint8_t>(
response_data, response_data + java_response_position);
auto message_response = std::move(it->second);
pending_responses_.erase(it);
message_response->Complete(
std::make_unique<fml::DataMapping>(std::move(response)));
}
Copy the code
Finally, call the Complete() method:
5.4 platform_message_response_dart
### platform_message_response_dart.cc
void PlatformMessageResponseDart::Complete(std::unique_ptr<fml::Mapping> data) {
if (callback_.is_empty())
return; FML_DCHECK(! is_complete_); is_complete_ =true;
ui_task_runner_->PostTask(fml::MakeCopyable(
[callback = std::move(callback_), data = std::move(data)]() mutable {
std::shared_ptr<tonic::DartState> dart_state =
callback.dart_state().lock();
if(! dart_state)return;
tonic::DartState::Scope scope(dart_state);
Dart_Handle byte_buffer = WrapByteData(std::move(data));
tonic::DartInvoke(callback.Release(), {byte_buffer});
}));
}
Copy the code
One thing to note is that the callback is called in the UI thread, and the callback data is passed through WrapByteData serialization.
Dart this callback should be the same callback registered in window.dart:
### window.dart
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) {
final String error =
_sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
if(error ! = null) throw new Exception(error); } /// Wraps the given [callback]in another callback that ensures that the
/// original callback is called in the zone it was registered in.
static PlatformMessageResponseCallback _zonedPlatformMessageResponseCallback(PlatformMessageResponseCallback callback) {
if (callback == null)
return null;
// Store the zone in which the callback is being registered.
final Zone registrationZone = Zone.current;
return (ByteData data) {
registrationZone.runUnaryGuarded(callback, data);
};
}
Copy the code
The final reflection at the Dart level is the control callback of the Future through the Completer.
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: 'during a platform message response callback')); }});Copy the code
6.0 the thread
6.1 TaskRunner
— MessageLoop
If you look at TaskRunner, when you construct it, you pass in a MessageLoopImpl,
### task_runner.h
namespace fml {
class MessageLoopImpl;
class TaskRunner : public fml::RefCountedThreadSafe<TaskRunner> {
public:
virtual ~TaskRunner();
virtual void PostTask(fml::closure task);
virtual void PostTaskForTime(fml::closure task, fml::TimePoint target_time);
virtual void PostDelayedTask(fml::closure task, fml::TimeDelta delay);
virtual bool RunsTasksOnCurrentThread();
static void RunNowOrPostTask(fml::RefPtr<fml::TaskRunner> runner,
fml::closure task);
protected:
TaskRunner(fml::RefPtr<MessageLoopImpl> loop);
private:
fml::RefPtr<MessageLoopImpl> loop_;
FML_FRIEND_MAKE_REF_COUNTED(TaskRunner);
FML_FRIEND_REF_COUNTED_THREAD_SAFE(TaskRunner);
FML_DISALLOW_COPY_AND_ASSIGN(TaskRunner);
};
}
Copy the code
When you see MessageLoop, you hold a task_runner_ and loop_,
### message_loop.h
class MessageLoop {
public:
FML_EMBEDDER_ONLY
static MessageLoop& GetCurrent();
bool IsValid() const;
void Run();
void Terminate();
void AddTaskObserver(intptr_t key, fml::closure callback);
void RemoveTaskObserver(intptr_t key);
fml::RefPtr<fml::TaskRunner> GetTaskRunner() const;
// Exposed for the embedder shell which allows clients to poll for events
// instead of dedicating a thread to the message loop.
void RunExpiredTasksNow();
static void EnsureInitializedForCurrentThread();
static bool IsInitializedForCurrentThread();
~MessageLoop();
private:
friend class TaskRunner;
friend class MessageLoopImpl;
fml::RefPtr<MessageLoopImpl> loop_;
fml::RefPtr<fml::TaskRunner> task_runner_;
MessageLoop();
fml::RefPtr<MessageLoopImpl> GetLoopImpl() const;
FML_DISALLOW_COPY_AND_ASSIGN(MessageLoop);
};
}
Copy the code
If you look at the assignment to the above two variables in the constructor, you can see that an instance of MessageLoopImpl is constructed and passed to the TaskRunner
### message_loop.cc
MessageLoop::MessageLoop()
: loop_(MessageLoopImpl::Create()),
task_runner_(fml::MakeRefCounted<fml::TaskRunner>(loop_)) {
FML_CHECK(loop_);
FML_CHECK(task_runner_);
}
Copy the code
Go back to TaskRunner.PostTask
### task_runner.cc
void TaskRunner::PostTask(fml::closure task) {
loop_->PostTask(std::move(task), fml::TimePoint::Now());
}
Copy the code
Will eventually through loop_ namely MessageLoopImpl PostTask, then push to the delayed_tasks_,
### message_loop_impl.ccvoid MessageLoopImpl::PostTask(fml::closure task, fml::TimePoint target_time) { FML_DCHECK(task ! = nullptr); RegisterTask(task, target_time); } void MessageLoopImpl::RegisterTask(fml::closure task, fml::TimePoint target_time) { FML_DCHECK(task ! = nullptr);if (terminated_) {
// If the message loop has already been terminated, PostTask should destruct
// |task| synchronously within this function.
return;
}
std::lock_guard<std::mutex> lock(delayed_tasks_mutex_);
delayed_tasks_.push({++order_, std::move(task), target_time});
WakeUp(delayed_tasks_.top().target_time);
}
Copy the code
And delayed_tasks_ is a priority queue:
### message_loop_impl.h
using DelayedTaskQueue = std::
priority_queue<DelayedTask, std::deque<DelayedTask>, DelayedTaskCompare>;
DelayedTaskQueue delayed_tasks_;
Copy the code
To summarize, TaskRunner.PostTask ultimately puts tasks into queues in MessageLoop.
6.2 Thread
— ThreadHost
Who is responsible for fetching and running tasks that are put in queues? It’s time for the thread to come out. Let’s go to the Shell and look at the thread creation:
### shell_benchmarks.ccnamespace shell { static void StartupAndShutdownShell(benchmark::State& state, bool measure_startup, bool measure_shutdown) { std::unique_ptr<Shell> shell; std::unique_ptr<ThreadHost> thread_host; { benchmarking::ScopedPauseTiming pause(state, ! measure_startup); blink::Settings settings = {}; settings.task_observer_add = [](intptr_t, fml::closure) {}; settings.task_observer_remove = [](intptr_t) {}; // Measure the time it takes to setup the threads as well. thread_host = std::make_unique<ThreadHost>("io.flutter.bench.", ThreadHost::Type::Platform |
ThreadHost::Type::GPU | ThreadHost::Type::IO |
ThreadHost::Type::UI);
blink::TaskRunners task_runners(
"test", thread_host->platform_thread->GetTaskRunner(),
thread_host->gpu_thread->GetTaskRunner(),
thread_host->ui_thread->GetTaskRunner(),
thread_host->io_thread->GetTaskRunner());
shell = Shell::Create(
std::move(task_runners), settings,
[](Shell& shell) {
return std::make_unique<PlatformView>(shell, shell.GetTaskRunners());
},
[](Shell& shell) {
returnstd::make_unique<Rasterizer>(shell.GetTaskRunners()); }); } FML_CHECK(shell); { benchmarking::ScopedPauseTiming pause(state, ! measure_shutdown); shell.reset(); // Shutdown is synchronous. thread_host.reset(); } FML_CHECK(! shell); }... }Copy the code
Taskrunners are drawn from threadhosts.
### thread_host.h
namespace shell {
struct ThreadHost {
enum Type {
Platform = 1 << 0,
UI = 1 << 1,
GPU = 1 << 2,
IO = 1 << 3,
};
std::unique_ptr<fml::Thread> platform_thread;
std::unique_ptr<fml::Thread> ui_thread;
std::unique_ptr<fml::Thread> gpu_thread;
std::unique_ptr<fml::Thread> io_thread;
ThreadHost();
ThreadHost(ThreadHost&&);
ThreadHost& operator=(ThreadHost&&) = default;
ThreadHost(std::string name_prefix, uint64_t type_mask);
~ThreadHost();
void Reset();
};
}
Copy the code
There are four main threads: platform_thread, UI_thread, gpu_thread, io_thread.
### thread.cc
Thread::Thread(const std::string& name) : joined_(false) {
fml::AutoResetWaitableEvent latch;
fml::RefPtr<fml::TaskRunner> runner;
thread_ = std::make_unique<std::thread>([&latch, &runner, name]() -> void {
SetCurrentThreadName(name);
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto& loop = MessageLoop::GetCurrent();
runner = loop.GetTaskRunner();
latch.Signal();
loop.Run();
});
latch.Wait();
task_runner_ = runner;
}
Copy the code
Specific logical steps:
-
- The logic to set the thread name is in
ThreadHost
In whichname_prefix
Is in theshell_benchmarks
Defined in theio.flutter.bench.
- The logic to set the thread name is in
ThreadHost::ThreadHost(std::string name_prefix, uint64_t mask) {
if (mask & ThreadHost::Type::Platform) {
platform_thread = std::make_unique<fml::Thread>(name_prefix + ".platform");
}
if (mask & ThreadHost::Type::UI) {
ui_thread = std::make_unique<fml::Thread>(name_prefix + ".ui");
}
if (mask & ThreadHost::Type::GPU) {
gpu_thread = std::make_unique<fml::Thread>(name_prefix + ".gpu");
}
if (mask & ThreadHost::Type::IO) {
io_thread = std::make_unique<fml::Thread>(name_prefix + ".io"); }}Copy the code
- 2. The initialization
MessageLoop
### thread.cc
fml::MessageLoop::EnsureInitializedForCurrentThread()
Copy the code
### message_loop.cc
FML_THREAD_LOCAL ThreadLocal tls_message_loop([](intptr_t value) {
delete reinterpret_cast<MessageLoop*>(value);
});
void MessageLoop::EnsureInitializedForCurrentThread() {
if(tls_message_loop.Get() ! = 0) { // Already initialized.return;
}
tls_message_loop.Set(reinterpret_cast<intptr_t>(new MessageLoop()));
}
Copy the code
A ThreadLocal holds MessageLoop to avoid repeated creation.
- 3. Get
loop
andrunner
### thread.cc
auto& loop = MessageLoop::GetCurrent();
runner = loop.GetTaskRunner();
Copy the code
### message_loop.cc
MessageLoop& MessageLoop::GetCurrent() { auto* loop = reinterpret_cast<MessageLoop*>(tls_message_loop.Get()); FML_CHECK(loop ! = nullptr) <<"MessageLoop::EnsureInitializedForCurrentThread was not called on "
"this thread prior to message loop use.";
return *loop;
}
fml::RefPtr<fml::TaskRunner> MessageLoop::GetTaskRunner() const {
return task_runner_;
}
Copy the code
- 4.run messageloop
The last call
### thread.cc
loop.Run();
Copy the code
### message_loop.cc
void MessageLoop::Run() {
loop_->DoRun();
}
Copy the code
Loop_ is MessageLoopImpl:
### message_loop_impl.cc
void MessageLoopImpl::DoRun() {
if (terminated_) {
// Message loops may be run only once.
return;
}
// Allow the implementation to do its thing.
Run();
// The loop may have been implicitly terminated. This can happen if the
// implementation supports termination via platform specific APIs or just
// error conditions. Set the terminated flag manually.
terminated_ = true;
// The message loop is shutting down. Check if there are expired tasks. This
// is the last chance for expired tasks to be serviced. Make sure the
// terminated flag is already set so we don't accrue additional tasks now. RunExpiredTasksNow(); // When the message loop is in the process of shutting down, pending tasks // should be destructed on the message loop's thread. We have just returned
// from the implementations |Run| method which we know is on the correct
// thread. Drop all pending tasks on the floor.
std::lock_guard<std::mutex> lock(delayed_tasks_mutex_);
delayed_tasks_ = {};
}
Copy the code
The key is the Run() method, but unfortunately in MessageLoopImpl Run() is pure virtual:
class MessageLoopImpl : public fml::RefCountedThreadSafe<MessageLoopImpl> { public: static fml::RefPtr<MessageLoopImpl> Create(); virtual ~MessageLoopImpl(); virtual void Run() = 0; void PostTask(fml::closure task, fml::TimePoint target_time); void DoRun(); . }Copy the code
There are different implementations depending on the platform:
fml::RefPtr<MessageLoopImpl> MessageLoopImpl::Create() {
#if OS_MACOSX
return fml::MakeRefCounted<MessageLoopDarwin>();
#elif OS_ANDROID
return fml::MakeRefCounted<MessageLoopAndroid>();
#elif OS_LINUX
return fml::MakeRefCounted<MessageLoopLinux>();
#elif OS_WIN
return fml::MakeRefCounted<MessageLoopWin> ();#else
return nullptr;
#endif
Copy the code
MessageLoopAndroid:
### message_loop_android.cc
void MessageLoopAndroid::Run() {
FML_DCHECK(looper_.get() == ALooper_forThread());
running_ = true;
while (running_) {
int result = ::ALooper_pollOnce(-1, // infinite timeout
nullptr, // out fd,
nullptr, // out events,
nullptr // out data
);
if (result == ALOOPER_POLL_TIMEOUT || result == ALOOPER_POLL_ERROR) {
// This handles the case where the loop is terminated using ALooper APIs.
running_ = false; }}}Copy the code
At this point, ALooper_pollOnce is no longer in the loop, so the logic is to loop tasks out of the priority queue.
If you see the loop that Android developers are familiar with,
TaskRunner
Similar to theHandler
, which can be used to throw messages to the queue;MessageLooperImpl
It’s sort of likeMessageQueue
;- while
MessageLoopAndroid
Is similar to theLooper
, loop to retrieve messages from the queue
Refs:
- Because Chinese website
- Asynchronism in Flutter/Dart
- The secret between Flutter and primal