Flutter是一个跨平台的方案,在UI、触控及基本的网络请求上已经基本做到平台无关,但是在某些平台特性的功能上,还是必须要对不同的平台做处理。这就涉及到与Native的通信。
Flutter提供了一套Platform Channel的机制,来满足与Native通信的功能要求。
Platf Channel分为BasicMessageChannel、MethodChannel以及EventChannel三种。其各自的主要用途如下:
其实可以看到,无论传方法还是传事件,其本质上都是数据的传递,不过上层包的一些逻辑不同而已。所以这三个Channel的通信实现基本是一致的,只是EventChannel在处理消息处理时会有一些特殊的附加逻辑,这个后文会做分析。
几个Channel的用法都很简单,就简单介绍MethodChannel的基本用法,之后MethodChannel为例分析一下Flutter是怎么和Native做到通信的。
用官方的Demo,Flutter获取设备电量。
在Native项目的Activity中,注册插件:
// Flutter在Native上是用一个SurfaceView承载的,getFlutterView()获取到这个View
// BATTERY_CHANNEL是channel名称,必须唯一一般用包名加功能命名,如:"com.qq.fm/battery"
new MethodChannel(getFlutterView(), BATTERY_CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getBatteryLevel")) {
int batteryLevel = getBatteryLevel(); // 调系统API获取电量
if (batteryLevel != -1) {
result.success(batteryLevel);
} else {
result.error("UNAVAILABLE", "Battery level not available.", null);
}
} else {
result.notImplemented();
}
}
}
);
然后在Flutter项目中,直接通过ChannelName实例化MethodChannel,然后调用方法即可。
MethodChannel methodChannel =MethodChannel('com.qq.fm/battery');
final int result = await methodChannel.invokeMethod('getBatteryLevel');
可以看到,这里就是在Native项目中注册一下MethodChannel,然后就可以在Flutter中用同样的ChannelName实例化一个MethodChannel,然后发起调用。
需要补充的是invokeMethod
方法除了函数名,还可以带上参数传递过去。Native层则是通过Result类的相关方法,将结果回传给Flutter。
我们先看一下Native项目中那些代码到底做了什么事情。
// MethodChannel.java
// 平平无奇的构造方法(默认codec是StandardMethodCodec,先可以简单理解codec就是一种数据编码方式)
public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
// ...
this.messenger = messenger;
this.name = name;
this.codec = codec;
}
// 设置handler时,会将name和handler一起传给messager。
public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
this.messenger.setMessageHandler(this.name, handler == null ? null : new MethodChannel.IncomingMethodCallHandler(handler));
}
// IncomingMethodCallHandler实现了BinaryMessageHandler接口,会将message处理转化为MethodCall后传给handler。
private final class IncomingMethodCallHandler implements BinaryMessageHandler {
// ...
public void onMessage(ByteBuffer message, final BinaryReply reply) {
// ...
}
}
回看一下前面的实际代码,messenger其实就是FlutterView
,BinaryMessenger
在Android里是一个接口,FlutterView
和FlutterNativeView
实现了它,实际上FlutterView
只是wrap了一下FlutterNativeView
,所以只用关注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);
}
}
mMessageHandlers其实是一个HashMap
,key就是ChannelName(这也是为什么ChannelName必须要唯一的原因)。
平台的逻辑就是这些了,好像没什么和通信有关的,只是维护了一个ChannelName和对应Handler的Map。
再看看Flutter发起调用时,做了什么事情。
// platform_channel.dart
// 构造方法没啥好看的
const MethodChannel(this.name, [this.codec = const StandardMethodCodec()]);
Future<dynamic> invokeMethod(String method, [dynamic arguments]) async {
assert(method != null);
final dynamic result = await BinaryMessages.send( // focus
name,
codec.encodeMethodCall(MethodCall(method, arguments)),
);
if (result == null)
throw MissingPluginException('No implementation found for method $method on channel $name');
return codec.decodeEnvelope(result);
}
可以看到,invoke里面,主要是把方法名和参数通过codec转化为二进制数据,通过BinaryMessages send出去,并等待这个方法返回result。
跟踪到BinaryMessages
:
// platform_messages.dart
static Future<ByteData> send(String channel, ByteData message) {
final _MessageHandler handler = _mockHandlers[channel];
if (handler != null) // (1)
return handler(message);
return _sendPlatformMessage(channel, message); // (2)
}
// 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);
}
String _sendPlatformMessage(String name,
PlatformMessageResponseCallback callback,
ByteData data) native 'Window_sendPlatformMessage';
注释(1)
处,_mockHandlers是一个<String, _MessageHandler>
的Map,可以在Flutter中mock住某个channelName,这样的话,发送这类消息就会走你自己的handler。我们要和Native做通信,这里自然没有mock住。
继续看(2)
的代码,从方法名就可以看出,这里就是和平台通信有关的逻辑了,跟踪进去,这里会处理一下回调,及错误情况,不过主要的是调用了ui.window.sendPlatformMessage()
这个方法,这是FlutterEngine中的库方法,在这个方法里会调用dart的Native方法。
Flutter Engine是开源的,我们可以继续跟下去。
Dart的Native机制是先注册一个对Native方法的映射表:
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},
});
}
我们可以找到对应的方法是_SendPlatformMessage
,这个方法会调到SendPlatformMessage
Dart_Handle SendPlatformMessage(Dart_Handle window,
const std::string& name,
Dart_Handle callback,
const tonic::DartByteData& data) {
// ...
if (Dart_IsNull(data.dart_handle())) {
dart_state->window()->client()->HandlePlatformMessage( // (1)
fml::MakeRefCounted<PlatformMessage>(name, response));
} else {
const uint8_t* buffer = static_cast<const uint8_t*>(data.data());
dart_state->window()->client()->HandlePlatformMessage( // (1)
fml::MakeRefCounted<PlatformMessage>(
name, std::vector<uint8_t>(buffer, buffer + data.length_in_bytes()),
response));
}
return Dart_Null();
}
可以看(1)
位置,最后都是会调到WindowClient的HandlePlatformMessage
方法,WindowClient的具体实现是RuntimeController,然后RuntimeController会将方法交给RuntimeDelegate来实现,而RuntimeDelegate的具体实现则是Engine类,这个类中的方法实现如下:
static constexpr char kAssetChannel[] = "flutter/assets";
void Engine::HandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message) {
if (message->channel() == kAssetChannel) {
HandleAssetPlatformMessage(std::move(message));
} else {
delegate_.OnEngineHandlePlatformMessage(std::move(message));
}
}
这个方法会首先检查是否是Flutter获取平台资源的,如果是就转到获取资源的逻辑里去,否则走Delegate的逻辑,这里Delegate是的实现类是Shell。
// |shell::Engine::Delegate|
void Shell::OnEngineHandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message) {
FML_DCHECK(is_setup_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
task_runners_.GetPlatformTaskRunner()->PostTask(
[view = platform_view_->GetWeakPtr(), message = std::move(message)]() {
if (view) {
view->HandlePlatformMessage(std::move(message));
}
});
}
可以看到,在Shell类里,会在PlatformTaskRunner
中添加一个task,这时候执行会切换到Platform task,之前都是在UI task中执行的。PlatformView
的HandlePlatformMessage
是一个虚函数,可以看PlatformViewAndroid
中对其的实现。
// |shell::PlatformView|
void PlatformViewAndroid::HandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message) {
JNIEnv* env = fml::jni::AttachCurrentThread();
fml::jni::ScopedJavaLocalRef<jobject> view = java_object_.get(env);
// ...
auto java_channel = fml::jni::StringToJavaString(env, message->channel()); // (1)
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); // (2)
} else {
message = nullptr;
// This call can re-enter in InvokePlatformMessageXxxResponseCallback.
FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),
nullptr, response_id); // (3)
}
}
这里,可以看到,已经开始进行JNI相关的操作了。注释(1)
处从message结构中,拿到ChannelName,然后(2)
和(3)
的区别就是一个带参数,一个不带参数,通过FlutterViewHandlePlatformMessage
发起调用。
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); // (1)
FML_CHECK(CheckException(env));
}
// ...
bool PlatformViewAndroid::Register(JNIEnv* env) {
// ...
g_flutter_native_view_class = new fml::jni::ScopedJavaGlobalRef<jclass>(
env, env->FindClass("io/flutter/view/FlutterNativeView")); // (2)
// ...
g_handle_platform_message_method =
env->GetMethodID(g_flutter_native_view_class->obj(),
"handlePlatformMessage", "(Ljava/lang/String;[BI)V"); // (3)
// ...
}
从(1)
处可以看到,开始通过JNI调Java方法,注释(2)
和(3)
处,指定了Java类和方法。调用了FlutterNativeView
的handlePlatformMessage
方法,看这里的源码:
private void handlePlatformMessage(final String channel, byte[] message, final int replyId) {
this.assertAttached();
BinaryMessageHandler handler = (BinaryMessageHandler)this.mMessageHandlers.get(channel); // (1)
if (handler != null) {
try {
ByteBuffer buffer = message == null ? null : ByteBuffer.wrap(message);
handler.onMessage(buffer, new BinaryReply() {
// ...
});
} catch (Exception var6) {
// ...
}
} else {
Log.e("FlutterNativeView", "Uncaught exception in binary message listener", var6);
nativeInvokePlatformMessageEmptyResponseCallback(this.mNativePlatformView, replyId);
}
}
看注释(1)
处,就是从之前存入的map中取出相应的handler,如果找不到,则回调Native表示失败。否则调用BinaryMessageHandler
的onMessage
方法,将方法信息传递过去。
然后就回到了最开始的逻辑,通过IncomingMethodCallHandler
处理message数据,然后将结果通过nativeInvokePlatformMessageResponseCallback
回调给Flutter。
跟完MethodChannel
的源码,会发现整个通信机制还挺简单的,先去不去理解Codec的话,等于就是将dart的变量,传到dart Native,然后交到java Native, 再传到java。然后相反的路径,再从java到dart。
然后再去看BasicMessageChannel
就是没有MethodCall
这个结构的,其他的也是走的BinaryMessages.send
方法。然后在Android端,没有IncomingMethodCallHandler
这个类,直接就是BinaryMessageHandler
。所以了解了MethodChannel
,BasicMessageChannel
原理自然就懂了。(可以说MethodChannel就是借助BasicMessageChannel实现的)
同样的EventChannel
则是基于MethodChannel
来实现的,只是两端的handler会有一些特殊的处理方式,这个倒是与通信没有多大关系了,不过设计的也很简单,比较有意思。之后另起一文细表。
Wrote by Kevin(a2V2aW56aGFuMDQxN0BvdXRsb29rLmNvbQ==)
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。