本文主要是介绍FlutterPlugin,涉及到原理和使用。
Flutter Plugin提供Android或者iOS的底层封装,在Flutter层提供组件功能,使Flutter可以较方便的调取Native的模块。很多平台相关性或者对于Flutter实现起来比较复杂的部分,都可以封装成Plugin。
那么flutter和native如何相互调用呢,原理如下
消息在client和host之间通过平台通道(platform channels)来进行的,之间的通讯都是异步
的。
我们先介绍下MethodChannel的实现原理,使用方式以Android端为例:
//1、注册通道
MethodChannel channel = new MethodChannel(getFlutterView(),"flutter/channel");
//2、设置回调,被call回调
channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
//通知到flutter端
result.success(“返回值”);
}
});
//3、主动call flutter的方法
channel.invokeMethod("callFlutter", "params", new MethodChannel.Result() {
@Override
public void success(Object result) {
//呼叫成功
}
@Override
public void error(String errorCode, String errorMessage, Object errorDetails) {
//呼叫出现异常
}
@Override
public void notImplemented() {
//flutter没有对应的实现方法
}
});
其实MethodChannel
中的构造函数第一个参数就是BinaryMessenger
,这个BinaryMessenger
是在DartExecuter
中初始化的,而BinaryMessenger
的实现类中的send函数
最终是对DartMessenger
的一层包装,所以我们看消息是如何发送出去的,直接看DartMessenger
的send函数
@Override
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,然后把方法回调和replyId加入到一个map,如果没有就直接发送消息了。
如果消息体为空,就发送一个空消息,如果有消息内容,就把消息发送过去。还会携带一个replyId,这个会在回调的时候有用。发送消息是通过flutterJni
分发方法,最终调到了c++层面,通过c++的dart虚拟机,把消息传递给了flutter。
如何收到flutter端的消息呢
同样的接受消息也是在DartExecutor
,通过onAttachToJNI
会建立通道关联,同样的消息会经过DartMessenger
,如果是收到原生调用flutter以后的回复,会调用
@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);
}
}
}
首先根据replyId找到刚才发送消息时保存的callback,找到了,就把reply转化为bytebuff调用出去。BinaryReply会将消息回调回去。
如果是从flutter主动调动的消息稍微负责一些,也是类似的原理,只不过多了一步根据通道名字找到对象的处理类。
//三个参数,通道名字,消息的字节,从flutter端传递过来的replyId,
public void handleMessageFromDart(
@NonNull final String channel,
@Nullable byte[] message,
final int replyId
) {
Log.v(TAG, "Received message from Dart over channel '" + channel + "'");
//根据通道名字找到对象的处理类"
BinaryMessenger.BinaryMessageHandler handler = messageHandlers.get(channel);
if (handler != null) {
try {
Log.v(TAG, "Deferring to registered handler to process message.");
//解码数据并调用onMessage传递消息 ,注意一下这个Reply
final ByteBuffer buffer = (message == null ? null : ByteBuffer.wrap(message));
handler.onMessage(buffer, new Reply(flutterJNI, replyId));
} catch (Exception ex) {
Log.e(TAG, "Uncaught exception in binary message listener", ex);
//异常处理
flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
} else {
Log.v(TAG, "No registered handler for message. Responding to Dart with empty reply message.");
//未实现通道处理
flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
}
像Flutter官方提供的shared_preferences,就是通过通道来实现的。
FlutterPlugin还有另外一个用法,就是直接在Flutter端由Native渲染。比如视频播放插件video_player。
通常有两种做法,一种是PlatformView,另外一种是Texture(俗称外接纹理)。其中PlatformView区分Android和iOS,在Android平上上叫做 AndroidView,而在iOS平台,叫UIKitView。
前面提到的MethodChannel
可以向flutter传输数据,但是如果将一个视频或者图片的数据传到flutter会很繁重,所以Flutter提供了一种基于Texture的数据共享机制,而以上两种方式原理都是基于Texture。
Texture https://api.flutter.dev/flutter/widgets/Texture-class.html
下图是视频插件的实现原理图