@TryLoveCatch
2019-08-28T19:18:08.000000Z
字数 16882
阅读 1934
flutter
Flutter如何和Native通信-Android视角
深入理解Flutter Platform Channel
底层原理 - 深入探索Platform Channel
Flutter Platform Channel 使用与源码分析
Flutter是可以同时在iOS和Android系统上运行的,那么就有一个问题,Flutter如何和Native进行通信呢?。比如说,Flutter app要显示手机的电量,而电量只能通过平台的系统Api获取。这时就需要有个机制使得Flutter可以通过某种方式来调用这个系统Api并且获得返回值。那么Flutter是如何做到的呢?答案是Platform Channels
通过Platform Channels传递的消息都是异步的
Flutter定义了三种不同类型的Channel,它们分别是:
channel的名字,可以理解为id,就是独一无二的存在,flutter和native保持一致即可。
消息信使,BinaryMessenger类型,BinaryMessenger是PlatformChannel与Flutter端的通信的工具,其通信使用的消息格式为二进制格式数据,BinaryMessenger在Android中是一个接口,它的实现类为FlutterNativeView。
Codec是消息编解码器,主要用于将二进制格式的数据转化为Handler能够识别的数据,Flutter定义了两种Codec:MessageCodec和MethodCodec。MessageCodec用于二进制格式数据与基础数据之间的编解码,BasicMessageChannel所使用的编解码器是MessageCodec。MethodChannel和EventChannel所使用的编解码均为MethodCodec。
Flutter定义了三种类型的Handler,它们与PlatformChannel类型一一对应,分别是MessageHandler、MethodHandler、StreamHandler。在使用PlatformChannel时,会为它注册一个Handler,PlatformChannel会将该二进制数据通过Codec解码为转化为Handler能够识别的数据,并交给Handler处理。当Handler处理完消息之后,会通过回调函数返回result,将result通过编解码器编码为二进制格式数据,通过BinaryMessenger发送回Flutter端。
public interface BinaryMessenger {
void send(String channel, ByteBuffer message);
void send(String channel, ByteBuffer message, BinaryReply callback);
void setMessageHandler(String channel, BinaryMessageHandler handler);
interface BinaryMessageHandler {
void onMessage(ByteBuffer message, BinaryReply reply);
}
interface BinaryReply {
void reply(ByteBuffer reply);
}
}
消息编解码器Codec主要用于将二进制格式的数据转化为Handler能够识别的数据,Flutter定义了两种Codec:MessageCodec和MethodCodec。
MessageCodec用于二进制格式数据与基础数据之间的编解码。BasicMessageChannel所使用的编解码器就是MessageCodec。
Android中,MessageCodec是一个接口,定义了两个方法:
public interface MessageCodec<T> {
ByteBuffer encodeMessage(T message);
T decodeMessage(ByteBuffer message);
}
MessageCodec有多个不同的实现:
原封不动的返回二进制数据,可以使传递内存数据块时在编解码阶段免于内存拷贝。Android中为ByteBuffer
字符串与二进制数据之间的编解码,其编码格式为UTF-8
基础数据与二进制数据之间的编解码,其支持基础数据类型以及列表、字典。
BasicMessageChannel的默认编解码器,其支持基础数据类型、二进制数据、列表、字典。
MethodCodec用于二进制数据与方法调用(MethodCall)和返回结果之间的编解码。MethodChannel和EventChannel所使用的编解码器均为MethodCodec。
MethodCodec用于MethodCall对象的编解码,一个MethodCall对象代表一次从Flutter端发起的方法调用。
public interface MethodCodec {
Object decodeEnvelope(ByteBuffer envelope)
MethodCall decodeMethodCall(ByteBuffer methodCall)
ByteBuffer encodeErrorEnvelope(String errorCode, String errorMessage, Object errorDetails)
ByteBuffer encodeMethodCall(MethodCall methodCall)
ByteBuffer encodeSuccessEnvelope(Object result)
}
MethodCall有2个成员变量:
public interface MethodCall {
Object arguments
String method
}
由于处理的是方法调用,故相比于MessageCodec,MethodCodec多了对调用结果的处理。
MethodCodec有两种实现:
JSONMethodCodec的编解码依赖于JSONMessageCodec,当其在编码MethodCall时,会先将MethodCall转化为字典{"method":method,"args":args}。其在编码调用结果时,会将其转化为一个数组,调用成功为[result],调用失败为[code,message,detail]。再使用JSONMessageCodec将字典或数组转化为二进制数据。
比如当前想要调用某个Channel的setVolum(5),其对应的MethodCall被被转成{"method":"setVolum","args":{"volum":5}}
MethodCodec的默认实现,StandardMethodCodec的编解码依赖于StandardMessageCodec,当其编码MethodCall时,会将method和args依次使用StandardMessageCodec编码,写入二进制数据容器。其在编码方法的调用结果时,若调用成功,会先向二进制数据容器写入数值0(代表调用成功),再写入StandardMessageCodec编码后的result。而调用失败,则先向容器写入数据1(代表调用失败),再依次写入StandardMessageCodec编码后的code,message和detail。
- | Codec | Desc |
---|---|---|
MessageCodec | StandardMessageCodec | 支持基础数据格式与二进制之间的转换,包括 Boolean 、Number、String、Byte、List、Map、数组等。 |
BinaryCodec | 支持二进制数据的直接传递,实际上是没有做任何编解码,可以通过它做大内存块的数据传递。 | |
StringCodec | 支持字符串(UTF-8)与二进制之间的转换。 | |
JSONMessageCodec | 支持把数据转换为JSON,再调用 StringCodec 的编解码。 | |
MethodCodec | StandardMethodCodec | 调用 StandardMessageCodec 传递方法的名字和参数。 |
JSONMethodCodec | 调用 JSONMessageCodec 传递方法的名字和参数。 |
当我们接收二进制格式消息并使用Codec将其解码为Handler能处理的消息后,就该Handler上场了。
Flutter定义了三种类型的Handler,与Channel类型一一对应。
我们向Channel注册一个Handler时,实际上就是向BinaryMessager注册一个与之对应的BinaryMessageHandler。当消息派分到BinaryMessageHandler后,Channel会通过Codec将消息解码,并传递给Handler处理。
MessageHandler用户处理字符串或者半结构化的消息,其onMessage方法接收一个T类型的消息,并异步返回一个相同类型result。
public static interface BasicMessageChannel.MessageHandler<T> {
void onMessage(T message, BasicMessageChannel.Reply<T> reply)
}
MethodHandler用于处理方法的调用,其onMessageCall方法接收一个MethodCall类型消息,并根据MethodCall的成员变量method去调用对应的API,当处理完成后,根据方法调用成功或失败,通过Result返回对应的结果。
public static interface MethodChannel.MethodCallHandler {
void onMethodCall(MethodCall call, MethodChannel.Result result)
}
StreamHandler与前两者稍显不同,用于事件流的通信,最为常见的用途就是Platform端向Flutter端发送事件消息。当我们实现一个StreamHandler时,需要实现其onListen和onCancel方法。而在onListen方法的入参中,有一个EventSink(其在Android是一个对象,iOS端则是一个block)。我们持有EventSink后,即可通过EventSink向Flutter端发送事件消息。
public static interface EventChannel.StreamHandler {
void onCancel(Object arguments)
void onListen(Object arguments, EventChannel.EventSink events)
}
public static interface EventChannel.EventSink {
void endOfStream()
void error(String errorCode, String errorMessage, Object errorDetails)
void success(Object event)
}
-- | name | messager | codec | handler |
---|---|---|---|---|
BasicMessageChannel | MessageCodec-StandardMessageCodec | MessageHandler | ||
MethodChannel | MethodCodec-StandardMethodCodec | MethodHandler | ||
EventChannel | MethodCodec-StandardMethodCodec | StreamHandler |
Dart代码:
第一步
static const methodChannel = MethodChannel("method_channel_sample");
第二步
Future<dynamic> getUserInfo(String method, {String userName}) async {
return await methodChannel.invokeMethod(method, userName);
}
第三步
MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: new Text('获取 snow 用户信息'),
onPressed: () {
getUserInfo("getInfo", userName: "snow")
..then((result) {
setState(() {
messageFromNative = result;
});
});
},
),
Android代码:
private void addMethodChannel() {
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();
}
}
});
}
Dart 调用 Android 代码分三步。
- 首先在 Dart 端定义 MethodChannel 名字为 method_channel_sample。
- 然后定义getUserInfo方法,传入要调用的方法名和参数。
- 最后点击按钮执行方法,获取用户信息。
在 Android 端定一个 MethodChannel 名字和 Dart 端保持一致。设置 MethodCallHandler。当调用的是getInfo方法时,根据参数返回信息。
Android代码:
private void invokeSayHelloMethod() {
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() {
}
});
}
dart端代码:
Future<dynamic> addHandler(MethodCall call) async {
switch (call.method) {
case "sayHello":
return "Hello from Flutter";
break;
}
}
@override
void initState() {
super.initState();
methodChannel.setMethodCallHandler(addHandler);
}
在 Dart 端设置 MethodCallHandler 然后在 Android 端调用即可
dart端代码:
第一步
static const basicMessageChannel = BasicMessageChannel(
"basic_message_channel_sample", StandardMessageCodec());
第二步
Future<dynamic> sayHelloToNative(String message) async {
String reply = await basicMessageChannel.send(message);
setState(() {
msgReplyFromNative = reply;
});
return reply;
}
第三步
MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: new Text('say hello to native'),
onPressed: () {
sayHelloToNative("hello");
},
),
Android端代码:
private void addBasicMessageChannel() {
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");
});
}
Dart 向 Android 发送消息依然分为三步。
- 首先在 Dart 端定义 BasicMessageChannel 名字为 basic_message_channel_sample。
- 然后定义发送消息的方法sayHelloToNative。
- 最后点击按钮向 Android 端发送消息。
在 Android 端定一个 BasicMessageChannel 名字和 Dart 端保持一致。设置 MethodCallHandler。当收到消息时发一个回复。
Android端代码:
mBasicMessageChannel.send("native say hello to flutter");
dart端代码:
Future<dynamic> addHandler(Object result) async {
setState(() {
msgReceiveFromNative = result.toString();
});
}
void addMessageListener() {
basicMessageChannel.setMessageHandler(addHandler);
}
@override
void initState() {
super.initState();
addMessageListener();
}
在 Dart 端设置 MessageHandler 然后在 Android 端直接发送消息即可。
dart端代码:
第一步
static 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);
}
Android端代码:
private void addEventChannel() {
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() {
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;
}
});
}
Dart 接受 Android stream event。
- 首先在 Dart 端定义 EventChannel 名字为 event_channel_sample。
- 然后设置receiveBroadcastStream监听,当 Android 端有消息发过来会回调_onEvent方法。
在 Android 端启动一个定时器,每隔3s向 Dart 端发送一次消息。
如下图,在 Dart 与 Platform 通信过程中,通过 channel name 找到对方,然后把消息通过 codec 进行编解码,最后通过 binaryMessenger 进行发送。
我们来说一下流程:
- 当我们初始化一个Channel,也就是new的时候,会传入name,这个name就是这个channel的唯一标示。
- 当我们向该Channel注册处理消息的Handler时,实际上会生成一个BinaryMessageHandler,并以channel name为key,注册到BinaryMessenger中。也就是说一个channel,有一个唯一标示的name,有一个与之对应的BinaryMessageHandler。
- 当Flutter端发送消息到BinaryMessenger时,BinaryMessenger会根据其入参channel找到与之对应的BinaryMessageHandler,并交由其处理。
- Binarymessenger并不知道Channel的存在,它只和BinaryMessageHandler打交道。而Channel和BinaryMessageHandler则是一一对应的。
- 由于Channel从BinaryMessageHandler接收到的消息是二进制格式数据,无法直接使用,故Channel会将该二进制消息通过Codec(消息编解码器)解码为能识别的消息并传递给Handler进行处理。
- 当Handler处理完消息之后,会通过回调函数返回result,并将result通过编解码器编码为二进制格式数据,通过BinaryMessenger发送回Flutter端。
有没有觉得根Binder很像?我们来简化一下:
Android侧:
<name, BinaryMessageHandler>
Futter侧:
Android侧:
我们以MethodChannel为例,先来看使用:
// Dart层
static const methodChannel = MethodChannel("method_channel_sample");
Future<dynamic> getUserInfo() async {
return await methodChannel.invokeMethod("getInfo", {userName: "snow"});
}
=======================================================
// Andrid层
private void addMethodChannel() {
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();
}
}
});
}
这是一个简单的Dart调用Android的
class MethodChannel {
// 构造方法,通常我们只需要指定该channel的name,
const MethodChannel(this.name, [this.codec = const StandardMethodCodec()]);
// name作为通道的唯一标志付,用于区分不同的通道调用
final String name;
// 用于方法调用过程的编码
final MethodCodec codec;
// 用于发起异步平台方法调用,需要指定方法名,以及可选方法参数
Future<dynamic> invokeMethod(String method, [dynamic arguments]) async {
assert(method != null);
// 将一次方法调用中需要的方法名和方法参数封装为MethodCall对象,然后使用MethodCodec对该
// 对象进行进行编码操作,最后通过BinaryMessages中的send方法发起调用
final dynamic result = await BinaryMessages.send(
name,
codec.encodeMethodCall(MethodCall(method, arguments)),
);
if (result == null)
throw MissingPluginException('No implementation found for method $method on channel $name');
return codec.decodeEnvelope(result);
}
}
一般来说,我们会创建MethodChannel对后,调用其invokeMethod()方法用于向平台发起一次调用
在invokeMethod()方法中会将一次方法调中的方法名method和方法参数arguments封装为MethodCall对象
然后使用MethodCodec对其进行二进制编码
最后通过Dart层的BinaryMessages的send()发起平台方法调用请求
class BinaryMessages {
......
static Future<ByteData> send(String channel, ByteData message) {
final _MessageHandler handler = _mockHandlers[channel];
// 在没有设置Mock Handler的情况下,继续调用_sendPlatformMessage()
if (handler != null)
return handler(message);
return _sendPlatformMessage(channel, message);
}
static Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
final Completer<ByteData> completer = Completer<ByteData>();
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;
}
......
}
Dart层的BinaryMessages类中提供了用于发送和接受平台插件的二进制消息.最终会调用ui.window.sendPlatformMessage().
然后,会通过一系列的JNI方法,最后调用到Android层的BinaryMessenger里面的handleMessageFromDart(),而FlutterNativeView就是一个BinaryMessenger,所以它内部包含了一个Map,channel name和BinaryMessageHandler的对应关系
public class FlutterNativeView implements BinaryMessenger {
private final Map<String, BinaryMessageHandler> mMessageHandlers;
......
private final class PlatformMessageHandlerImpl implements PlatformMessageHandler {
// Called by native to send us a platform message.
public void handleMessageFromDart(final String channel, byte[] message, final int replyId) {
// 1.根据channel名称获取对应的BinaryMessageHandler对象.每个Channel对应一个
// Handler对象
BinaryMessageHandler handler = mMessageHandlers.get(channel);
if (handler != null) {
try {
// 2.将字节数组对象封装为ByteBuffer对象
final ByteBuffer buffer = (message == null ? null : ByteBuffer.wrap(message));
// 3.调用handler对象的onMessage()方法来分发消息
handler.onMessage(buffer, new BinaryReply() {
private final AtomicBoolean done = new AtomicBoolean(false);
@Override
public void reply(ByteBuffer reply) {
// 4.根据reply的情况,调用FlutterJNI中invokePlatformMessageXXX()方法将响应数据发送给Flutter层
if (reply == null) {
mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
} else {
mFlutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position());
}
}
});
} catch (Exception exception) {
mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
return;
}
mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
}
首先根据Channel名称从mMessageHandlers取出对应的二进制消息处理器BinaryMessageHandler
然后将字节数组message封装为ByteBuffer对象
然后调用BinaryMessageHandler实例的onMessage()方法处理ByteBuffer,并进行响应.
我们接下来看一下BinaryMessageHandler的onMessage()方法
public final class MethodChannel {
private final BinaryMessenger messenger;
private final String name;
private final MethodCodec codec;
......
public void setMethodCallHandler(final @Nullable MethodCallHandler handler) {
messenger.setMessageHandler(name,
handler == null ? null : new IncomingMethodCallHandler(handler));
}
public interface Result {
void success(@Nullable Object result);
void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails);
void notImplemented();
}
public interface MethodCallHandler {
void onMethodCall(MethodCall call, Result result);
}
private final class IncomingMethodCallHandler implements BinaryMessageHandler {
private final MethodCallHandler handler;
IncomingMethodCallHandler(MethodCallHandler handler) {
this.handler = handler;
}
@Override
public void onMessage(ByteBuffer message, final BinaryReply reply) {
// 1.使用codec对来自Flutter方法调用数据进行解码,并将其封装为MethodCall对象.
// MethodCall中包含两部分数据:method表示要调用的方法;arguments表示方法所需参数
final MethodCall call = codec.decodeMethodCall(message);
try {
// 2.调用自定义MethodCallHandler中的onMethodCall方法继续处理方法调用
handler.onMethodCall(call, new Result() {
@Override
public void success(Object result) {
// 调用成功时,需要回传数据给Flutter层时,使用codec对回传数据result
// 进行编码
reply.reply(codec.encodeSuccessEnvelope(result));
}
@Override
public void error(String errorCode, String errorMessage, Object errorDetails) {
// 调用失败时,需要回传错误数据给Flutter层时,使用codec对errorCode,
// errorMessage,errorDetails进行编码
reply.reply(codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
@Override
public void notImplemented() {
// 方法没有实现时,调用该方法后,flutter将会受到相应的错误消息
reply.reply(null);
}
});
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to handle method call", e);
reply.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
}
}
}
}
首先使用codec对来自Flutter层的二进制数据进行解码,并将其封装为MethodCall对象
然后调用MethodCallHandler的onMethodCall()方法.
接下来就是handler的处理,这个是我们自己的逻辑了:
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();
}
}
});
我们会返回一个字符串给Dart
Dart层 --> MethodChannel.invokeMethod() --> codec编码MethodCall,包含方法名和参数 --> BinaryMessages.send() --> JNI --> Android层 --> BinaryMessage.handleMessageFromDart() --> BinaryMessageHandler.onMessage() 解码MethodCall,拿到方法名和参数 --> Handler.onMethodCall() --> 调用方法,返回结果 --> BinaryMessageHandler 编码结果二进制 --> BinaryMessage --> JNI --> Dart层