Platform Channel Demo
Flutter是使用Dart语言开发,使用PUB管理package包,目前已经有很多可用的功能包,但是很多情况还是需要和Native代码进行交互,调用一些平台特偶的功能。Flutter提供了platform-specific API来和Native进行交互。
这张是官网上的图,描述了Flutter APP和Host平台之间是通过MethodChannel进行通信的。
Flutter -> Android
下面的代码是官网上的Demo,Flutter App通过MethodChannle调用Android获取电量。
Android提供服务:
Android层定义了一个指定名称的MethodChannel
,并且setMethodCallHandler
来接收Flutter端的请求。根据请求的方法名调用对应的方法。
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "samples.flutter.dev/battery";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
// Note: this method is invoked on the main thread.
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();
}
}
});
}
}
Flutter请求:
而Flutter端,使用同样的channel name创建了一个MethodChannel
, 并调用invokeMethod
方法告知Android层要调用的方法名和参数,并且可以获取结果。(异步调用)
class _MyHomePageState extends State<MyHomePage> {
static const platform = const MethodChannel('samples.flutter.dev/battery');
// Get battery level.
String _batteryLevel = 'Unknown battery level.';
Future<void> _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;
});
}
}
Android -> Flutter
网上基本都是Flutter调用Android的,基本没有Android调用Flutter方法的,甚至有的人写的blog说MethodChannle使用场景是Flutter调用Android。 其实Channel是双向的。 只是Android调用Flutter可能有一个问题,就是调用时FlutterView可能还没有初始化好。(经过测试,如果未初始化好,Android端会收到未实现的错误。)
Flutter提供服务:
代码和前面Android提供服务时基本一样的。但是_onMethodCall方法中并没有Result参数告知执行结果。
class FlutterTestChannel{
static const platform2 = const MethodChannel('com.test.flutter/common');
_FlutterPrintHandler() {
platform2.setMethodCallHandler(_onMethodCall);
}
Future<void> _onMethodCall(MethodCall call) {
switch(call.method) {
case 'print':
platform.invokeMethod("d", {"tag": "MyApp", "log": "Dart receive android call! "});
break;
default:
throw UnimplementedError('${call.method} was invoked but isn't implemented by PlatformViewsService');
}
return null;
}
Android请求:
Android端请求和之前Flutter也基本一致, 但是比Dart多了一个Result回调。
@Override
protected void onResume() {
super.onResume();
// android->Flutter
MethodChannel channel = new MethodChannel(getFlutterView(), "com.test.flutter/common");
channel.invokeMethod("print", "123", new MethodChannel.Result() {
@Override
public void success(Object result) {
}
@Override
public void error(String errorCode, String errorMessage, Object errorDetails) {
}
@Override
public void notImplemented() {
}
});
}
从上面代码可以看出:
– Channle需要有一个唯一的名字
– Channel通信是双向的
– Flutter约定了通信的规则
BinaryMessenger
Facility for communicating with Flutter using asynchronous message passing with binary messages.
之前在介绍FlutterView的时候我们有了解过它实现了BinaryMesenger
接口,这个接口定义如何同Flutter进行通信。下面是相关的类图。
![]()
主要包括4部分
1. BinaryMessenger: 定义了通信接口和格式
2. 上面的FlutterView等4个类实现了BinaryMessenger接口,他们是实际负责和Flutter进行数据交互的类
3. 中间黄色的3种Channel,内部都是使用BinaryMessenger来进行交互,只是进行了封装,适用于不同场景。
4. 最下面2层是用于把低层二进制数据转换为各自Channle使用的数据。
因为通信是双向的,所以这个接口定义了Send
方法用来从Android向Flutter发送数据,并且通过BinaryReply
内部接口接收Flutter的响应。同样,当收到Flutter发送过来的数据时,通过BinaryMessageHandler
接口进行回应。通信过程数据都是使用ByteBuffer
。当然最重要的是需指定发到那个channel。 需要注意的是,数据发送必须在主线程。
向Flutter发送数据
DartMessenger
从类图中可以看到,一共有4个类实现了BinaryMessage接口,也就是说这4个类都具有发送消息给Flutter的能力。去看一下内部实现发现调用关系是:FlutterView --> FlutterNativeView --> DartExecute --> DartMessenger
。真正实现调用的是DartMessenger(之前1.3版本中,我记得FlutterNativeView中是自己实现的调用,现在看最新的1.7.8已经都是由DartMessenger实现了)。最终通过FlutterJNI
调用到了C++的代码。
创建
public DartExecutor(@NonNull FlutterJNI flutterJNI) {
this.flutterJNI = flutterJNI;
this.messenger = new DartMessenger(flutterJNI);
messenger.setMessageHandler("flutter/isolate", isolateChannelMessageHandler);
}
DartMessenger是在创建DartExecutor时创建的,之前Flutter初始化流程中我们提到过DartExecutor是在创建FlutterView时创建的。 这里创建DartMessenger时设置了一个isolateChannelMessageHandler
来接收Flutter通过isolate channel发送的数据。
结构
private final FlutterJNI flutterJNI;
// 记录从Flutter接收对应channel数据的Handler
private final Map<String, BinaryMessenger.BinaryMessageHandler> messageHandlers;
// 记录发送给Fullter数据的回应的接口
private final Map<Integer, BinaryMessenger.BinaryReply> pendingReplies;
private int nextReplyId = 1;
在DartMessenger中定义了2个Map,用来存放所有的channel的BinaryMessageHandler
和BinaryReply
。所以setMessageHandler实现很简单:
public void setMessageHandler(@NonNull String channel, @Nullable BinaryMessenger.BinaryMessageHandler handler) {
if (handler == null) {
Log.v(TAG, "Removing handler for channel '" + channel + "'");
messageHandlers.remove(channel);
} else {
Log.v(TAG, "Setting handler for channel '" + channel + "'");
messageHandlers.put(channel, handler);
}
}
发送数据
public void send(
@NonNull String channel,
@Nullable ByteBuffer message,
@Nullable BinaryMessenger.BinaryReply callback
) {
Log.v(TAG, "Sending message with callback over channel '" + channel + "'");
int replyId = 0;
if (callback != null) {
replyId = nextReplyId++;
pendingReplies.put(replyId, callback);
}
if (message == null) {
flutterJNI.dispatchEmptyPlatformMessage(channel, replyId);
} else {
flutterJNI.dispatchPlatformMessage(channel, message, message.position(), replyId);
}
}
我们可以看到,如果callback不为空,每次发送一个数据,都有一个replyId (因为发送数据必须在UI线程,所以不需要进行同步)并且添加到pendingReplies中, 否则传0。最终调用flutterJNI来发送消息给C++层。下面是Native层的调用时序图 (这个是用markdown画的效果不太行:joy:):
PaltformView
其中PlatformView::DispatchPlatformMessage
方法接收的是PlatformMessage
参数, 所以这里对Java层传入数据进行了转换。 其中response_id就是Java层的replyId, 如果为0不创建PlatformMessageResponseAndroid
, 用于接收调用结果。
void PlatformViewAndroid::DispatchPlatformMessage(JNIEnv* env,
std::string name,
jobject java_message_data,
jint java_message_position,
jint response_id) {
uint8_t* message_data =
static_cast<uint8_t*>(env->GetDirectBufferAddress(java_message_data));
std::vector<uint8_t> message =
std::vector<uint8_t>(message_data, message_data + java_message_position);
// 创建了一个PlatformMessageResponseAndroid对象,其中java_object_是FlutterJNI对象
fml::RefPtr<flutter::PlatformMessageResponse> response;
if (response_id) {
response = fml::MakeRefCounted<PlatformMessageResponseAndroid>(
response_id, java_object_, task_runners_.GetPlatformTaskRunner());
}
// 创建PlatformMessage对象
PlatformView::DispatchPlatformMessage(
fml::MakeRefCounted<flutter::PlatformMessage>(
std::move(name), std::move(message), std::move(response)));
Shell
Shell在调用Engine时,使用了UI线程。
void Shell::OnPlatformViewDispatchPlatformMessage(
fml::RefPtr<PlatformMessage> message) {
FML_DCHECK(is_setup_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
task_runners_.GetUITaskRunner()->PostTask(
[engine = engine_->GetWeakPtr(), message = std::move(message)] {
if (engine) {
engine->DispatchPlatformMessage(std::move(message));
}
});
}
Engine
void Engine::DispatchPlatformMessage(fml::RefPtr<PlatformMessage> message) {
// 处理系统指定channel数据
if (message->channel() == kLifecycleChannel) {
if (HandleLifecyclePlatformMessage(message.get()))
return;
} else if (message->channel() == kLocalizationChannel) {
if (HandleLocalizationPlatformMessage(message.get()))
return;
} else if (message->channel() == kSettingsChannel) {
HandleSettingsPlatformMessage(message.get());
return;
}
// 处理其他channle数据
if (runtime_controller_->IsRootIsolateRunning() &&
runtime_controller_->DispatchPlatformMessage(std::move(message))) {
return;
}
// If there's no runtime_, we may still need to set the initial route.
if (message->channel() == kNavigationChannel)
HandleNavigationPlatformMessage(std::move(message));
}
从代码中可以看到一开始检查这个消息是否发给系统channel的,从定义中可以看到,系统目前有10个定义好的channel。
// 在Engine中
static constexpr char kAssetChannel[] = "flutter/assets";
static constexpr char kLifecycleChannel[] = "flutter/lifecycle";
static constexpr char kNavigationChannel[] = "flutter/navigation";
static constexpr char kLocalizationChannel[] = "flutter/localization";
static constexpr char kSettingsChannel[] = "flutter/settings";
static constexpr char kIsolateChannel[] = "flutter/isolate";
//其他engine/shell/platform/glfw/目录下的文件中
static constexpr char kFlutterPlatformChannel[] = "flutter/platform";
static constexpr char kTextInputChannel[] = "flutter/textinput";
static constexpr char kKeyEventChannel[] = "flutter/keyevent";
static constexpr char kAccessibilityChannel[] = "flutter/accessibility";
前面讲DartMessenger的时候有提到调用了 messenger.setMessageHandler("flutter/isolate", isolateChannelMessageHandler);
其实就是注册了对应的监听来接受Flutter从isolate发来的数据。而之前介绍FlutterView初始化的时候也见到了创建对应Channel的代码:
// Create all platform channels
navigationChannel = new NavigationChannel(dartExecutor);
keyEventChannel = new KeyEventChannel(dartExecutor);
lifecycleChannel = new LifecycleChannel(dartExecutor);
localizationChannel = new LocalizationChannel(dartExecutor);
platformChannel = new PlatformChannel(dartExecutor);
systemChannel = new SystemChannel(dartExecutor);
settingsChannel = new SettingsChannel(dartExecutor);
最后Engine把消息交给了Dart的Runtime去处理:
void Window::DispatchPlatformMessage(fml::RefPtr<PlatformMessage> message) {
std::shared_ptr<tonic::DartState> dart_state = library_.dart_state().lock();
if (!dart_state)
return;
tonic::DartState::Scope scope(dart_state);
// 把java/c++的数据转换为Dart的数据类型
Dart_Handle data_handle =
(message->hasData()) ? ToByteData(message->data()) : Dart_Null();
if (Dart_IsError(data_handle))
return;
int response_id = 0;
if (auto response = message->response()) {
response_id = next_response_id_++;
pending_responses_[response_id] = response;
}
// 传递给Dart
tonic::LogIfError(
tonic::DartInvokeField(library_.value(), "_dispatchPlatformMessage",
{tonic::ToDart(message->channel()), data_handle,
tonic::ToDart(response_id)}));
}
到这里消息被发送给了Dart去进行处理。注意,respones并没有传给dart,而是传递了response_id, 本地记录了id和response对象的关系。
从github下载的Dart和Flutter Engine中并没有
DartInvokeField
定义,从网上搜索了下发现fuchsia的源码中有
https://fuchsia.googlesource.com/tonic/+/master/logging/dart_invoke.cc 所以不清楚具体实现,但是可以推断是调用了Dart的 _dispatchPlatformMessage 方法,所以搜索这个方法名就行了。 Flutter中有很多这样的调用。
Dart接收消息
通过搜索发现这个Dart方法在:engine/lib/ui/hooks.dart
中。
@pragma('vm:entry-point')
void _dispatchPlatformMessage(String name, ByteData data, int responseId) {
if (window.onPlatformMessage != null) {
_invoke3<String, ByteData, PlatformMessageResponseCallback>(
window.onPlatformMessage,
window._onPlatformMessageZone,
name,
data,
(ByteData responseData) {
window._respondToPlatformMessage(responseId, responseData);
},
);
} else {
window._respondToPlatformMessage(responseId, null);
}
}
可以看到数据已经转换成了Dart的类型,并且进入到了Dart的执行环境。那么我们可以调试一下我们的程序,最终执行的堆栈如下:
可以看到Dart程序的入口就是我们从源码中找打这个方法。这里主要调用了_invoke3这个方法。看注释就是在给定的zone中执行回调方法,这个回调方法的参数也一起传递了。 简单了解了下Zones,最常见的使用Zone的情况是在异步代码中处理错误或异常信息。 这里我们先忽略这个。
/// Invokes [callback] inside the given [zone] passing it [arg1], [arg2] and [arg3].
void _invoke3<A1, A2, A3>(void callback(A1 a1, A2 a2, A3 a3), Zone zone, A1 arg1, A2 arg2, A3 arg3) {
if (callback == null)
return;
assert(zone != null);
if (identical(zone, Zone.current)) {
callback(arg1, arg2, arg3);
} else {
zone.runGuarded(() {
callback(arg1, arg2, arg3);
});
}
}
所以实际执行的方法是 window.onPlatformMessage
这个callback方法,在engine/lib/ui/window.dart
中定义如下
PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
PlatformMessageCallback _onPlatformMessage;
Zone _onPlatformMessageZone;
set onPlatformMessage(PlatformMessageCallback callback) {
_onPlatformMessage = callback;
_onPlatformMessageZone = Zone.current;
}
看起来有点复杂,其实_onPlatformMessage 是Window的一个私有成员变量,set和get提供了对私有成员的读写。这里调用的_onPlatformMessage其实是一个函数指针, 是通过set设置进来的。
/// Signature for [Window.onPlatformMessage].
typedef PlatformMessageCallback = void Function(String name, ByteData data, PlatformMessageResponseCallback callback);
看看第三个参数、也是一个函数指针
/// [Window.onPlatformMessage].
typedef PlatformMessageResponseCallback = void Function(ByteData data);
这个函数指针指向的是Windows下的_respondToPlatformMessage
函数,而这个函数调用的是C++层的代码,最终对应到C++层Window._RespondToPlatformMessage
方法
void _respondToPlatformMessage(int responseId, ByteData data)
native 'Window_respondToPlatformMessage';
回调Dart的处理函数
从前面堆栈看,最终是回调到了我们_onMethodCall
方法。 因为我们调用了MethodChannel.setMethodCallHandler
方法。
// platform_channel.dart
void setMethodCallHandler(Future<dynamic> handler(MethodCall call)) {
binaryMessenger.setMessageHandler(
name,
handler == null ? null : (ByteData message) => _handleAsMethodCall(message, handler),
);
}
const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), this.binaryMessenger = defaultBinaryMessenger ])
: assert(name != null),
assert(binaryMessenger != null),
assert(codec != null);
从上面可以看出,实际是调用了_DefaultBinaryMessenger
类的方法
// binary_messenger.dart
/// The default instance of [BinaryMessenger].
///
/// This is used to send messages from the application to the platform, and
/// keeps track of which handlers have been registered on each channel so
/// it may dispatch incoming messages to the registered handler.
const BinaryMessenger defaultBinaryMessenger = _DefaultBinaryMessenger._();
@override
void setMessageHandler(String channel, MessageHandler handler) {
if (handler == null)
_handlers.remove(channel);
else
_handlers[channel] = handler;
}
它也实现了handlePlatformMessage
方法,逻辑也很简单。
@override
Future<void> handlePlatformMessage(
String channel,
ByteData data,
ui.PlatformMessageResponseCallback callback,
) async {
ByteData response;
try {
final MessageHandler handler = _handlers[channel]; //找到对用channle的handler
if (handler != null)
response = await handler(data); //调用handler处理方法并返回结果
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message callback'),
));
} finally {
callback(response); //通过callback把结果返回给调用方
}
}
监听和回调
目前消息已经从Android发送到了Dart, 而Dart的Handler也已经准备好了,就看看他们是怎么联系起来的,也就是前面的Window.onPlatformMessage是如何被设置成 BinaryMessenger.handlePlatformMessage
的。 通过搜索代码终于找到原来在flutter_sdk/packages/flutter/lib/src/servicesbinding.dart
文件中,engine的源码中是没有的。
/// Listens for platform messages and directs them to the [defaultBinaryMessenger].
mixin ServicesBinding on BindingBase {
@override
void initInstances() {
super.initInstances();
_instance = this;
window
..onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage; //就是这里把监听和回调关联起来了。
initLicenses();
}
/// The current [ServicesBinding], if one has been created.
static ServicesBinding get instance => _instance;
static ServicesBinding _instance;
}
具体这个ServicesBinding是什么时候创建目前不太清楚,但是因为继承与BindingBase, 从构造函数看,应该是在Framework初始化时就设置了。
abstract class BindingBase {
/// Default abstract constructor for bindings.
///
/// First calls [initInstances] to have bindings initialize their
/// instance pointers and other state, then calls
/// [initServiceExtensions] to have bindings initialize their
/// observatory service extensions, if any.
BindingBase() {
developer.Timeline.startSync('Framework initialization');
assert(!_debugInitialized);
initInstances();
assert(_debugInitialized);
assert(!_debugServiceExtensionsRegistered);
initServiceExtensions();
assert(_debugServiceExtensionsRegistered);
developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
developer.Timeline.finishSync();
}
结果返回
从前面分析知道,Dart方法的结果会通过callback返回给调用方,而这个callback直接指向了C++的Window._RespondToPlatformMessage
方法。
void RespondToPlatformMessage(Dart_Handle window,
int response_id,
const tonic::DartByteData& data) {
if (Dart_IsNull(data.dart_handle())) {
UIDartState::Current()->window()->CompletePlatformMessageEmptyResponse(
response_id);
} else {
// TODO(engine): Avoid this copy.
const uint8_t* buffer = static_cast<const uint8_t*>(data.data());
UIDartState::Current()->window()->CompletePlatformMessageResponse(
response_id,
std::vector<uint8_t>(buffer, buffer + data.length_in_bytes()));
}
}
// 根据response_id 找到对用的 `PlatformMessageResponse`
void Window::CompletePlatformMessageResponse(int response_id,
std::vector<uint8_t> data) {
if (!response_id)
return;
auto it = pending_responses_.find(response_id);
if (it == pending_responses_.end())
return;
auto response = std::move(it->second);
pending_responses_.erase(it);
response->Complete(std::make_unique<fml::DataMapping>(std::move(data)));
}
根据response_id 找到PlatformView中之前保存的 PlatformMessageResponseAndroid
,执行Complete方法。
void PlatformMessageResponseAndroid::Complete(
std::unique_ptr<fml::Mapping> data) {
platform_task_runner_->PostTask(
fml::MakeCopyable([response = response_id_, //
weak_java_object = weak_java_object_, //
data = std::move(data) //
]() {
// We are on the platform thread. Attempt to get the strong reference to
// the Java object.
auto* env = fml::jni::AttachCurrentThread();
auto java_object = weak_java_object.get(env);
if (java_object.is_null()) {
// The Java object was collected before this message response got to
// it. Drop the response on the floor.
return;
}
// Convert the vector to a Java byte array.
fml::jni::ScopedJavaLocalRef<jbyteArray> data_array(
env, env->NewByteArray(data->GetSize()));
env->SetByteArrayRegion(
data_array.obj(), 0, data->GetSize(),
reinterpret_cast<const jbyte*>(data->GetMapping()));
// Make the response call into Java.
FlutterViewHandlePlatformMessageResponse(env, java_object.obj(),
response, data_array.obj());
}));
}
注释写的很清楚,先进行数据转换,然后把结果返回给Java层。 其中java_object是FlutterJNI对象。
static jmethodID g_handle_platform_message_response_method = nullptr;
void FlutterViewHandlePlatformMessageResponse(JNIEnv* env,
jobject obj,
jint responseId,
jobject response) {
env->CallVoidMethod(obj, g_handle_platform_message_response_method,
responseId, response);
FML_CHECK(CheckException(env));
}
这里调用的是g_handle_platform_message_response_method, 定义在了engine/shell/platform/android/platform_view_android_jni.cc
中
g_handle_platform_message_response_method = env->GetMethodID(
g_flutter_jni_class->obj(), "handlePlatformMessageResponse", "(I[B)V");
最终调用到FlutterJNI
private void handlePlatformMessageResponse(int replyId, byte[] reply) {
if (platformMessageHandler != null) {
platformMessageHandler.handlePlatformMessageResponse(replyId, reply);
}
// TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391)
}
而在创建FlatterView的时候会设置这个Handler,
public void onAttachedToJNI() {
Log.v(TAG, "Attached to JNI. Registering the platform message handler for this Dart execution context.");
flutterJNI.setPlatformMessageHandler(messenger);
}
用的就是messenger, 这个messenger就是DartMessenger, 这个根据之前的replyId,找到对应的BinaryReply 。
@Override
public void handlePlatformMessageResponse(int replyId, @Nullable byte[] reply) {
Log.v(TAG, "Received message reply from Dart.");
BinaryMessenger.BinaryReply callback = pendingReplies.remove(replyId);
if (callback != null) {
try {
Log.v(TAG, "Invoking registered callback for reply from Dart.");
callback.reply(reply == null ? null : ByteBuffer.wrap(reply));
} catch (Exception ex) {
Log.e(TAG, "Uncaught exception in binary message reply handler", ex);
}
}
}
对于我们的例子来说,因为使用的是MethodChannle,他继承了BinaryReply接口, 重写了reply方法。
private final class IncomingResultHandler implements BinaryReply {
private final Result callback;
IncomingResultHandler(Result callback) {
this.callback = callback;
}
@Override
@UiThread
public void reply(ByteBuffer reply) {
try {
if (reply == null) {
callback.notImplemented();
} else {
try {
callback.success(codec.decodeEnvelope(reply));
} catch (FlutterException e) {
callback.error(e.code, e.getMessage(), e.details);
}
}
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to handle method call result", e);
}
}
}
总结
这一篇主要以MethodChannel的例子分析了从Android调用Flutter方法并返回结果的整个流程。其实整体看来逻辑很简单:
1. Android和Flutter两端的各自BinaryMessenger实例内部都维护了channel name 和 channel 的对应关系
2. 通关channel name 让两端建立起了联系
3. 数据经过了JAVA — C++ — Dart的转换, 数据格式是可以定义的
4. send方法都如果设置了回调, 都会分配一个replayId,保证请求的结果可以返回
5. 消息发送都是在UI线程进行的