深入Flutter技术内幕:Platform Channel设计与实现(一)

2,478 阅读26分钟
原文链接: lionoggo.com
Flutter作为一个灵活的UI框架,引入Platform Channel机制来支持不同平台的API调用,无论是Android上的由Java或Kotlin开发的API,还是IOS上用Objective-C或Swift开发API.需要注意的是Platform Channel不依赖代码生成,而是建立在消息传递方式上.实际上,它的工作模式和原理非常类似基于二进制协议开发的网络服务,在下文中,我们会对此有更深的理解与体会.

Platform Channel简介

在Flutter中,提供了三种Platform Channel用来支持和平台之间数据的传递:

  • BasicMessageChannel: 支持字符串和半结构化的数据传递
  • MethodChannel: 支持方法调用,既可以从Flutter发平台发起方法调用,也可以从平台代码向Flutter发起调用
  • EventChannel: 支持数据流通信

这三种Platform Channel分别用不同的作用,但在设计上大同小异,他们都有以下三个成员变量:

  • name:表示Channel名字,每个Channel使用唯一的name作为其唯一标志
  • messager:信使,是消息的发送和接受工具
  • codec: 表示消息的编解码器,目前有MethodCodec和MessageCodec两种类型

Platform Channel基本结构

为了对这三种Platform Channel有个比较感性的认识,通过以下简化过的代码来熟悉它们的结构.比较熟悉的同学可以直接跳过此章节.

BasicMessageChannel

public final class BasicMessageChannel<T> {
private final BinaryMessenger messenger;
private final String name;
private final MessageCodec<T> codec;

public void setMessageHandler(final MessageHandler<T> handler) {
messenger.setMessageHandler(name,
handler == null ? null : new IncomingMessageHandler(handler));
}

public interface MessageHandler<T> {
void onMessage(T message, Reply<T> reply);
}

private final class IncomingMessageHandler implements BinaryMessageHandler {
private final MessageHandler<T> handler;

private IncomingMessageHandler(MessageHandler<T> handler) {
this.handler = handler;
}

@Override
public void onMessage(ByteBuffer message, final BinaryReply callback) {
......
handler.onMessage();
......
}
}
}

MethodChannel

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) {
......
handler.onMethodCall();
......
}
}
}

EventChannel

public final class EventChannel {
private final BinaryMessenger messenger;
private final String name;
private final MethodCodec codec;

public void setStreamHandler(final StreamHandler handler) {
messenger.setMessageHandler(name, handler == null ? null : new IncomingStreamRequestHandler(handler));
}

public interface StreamHandler {
void onListen(Object arguments, EventSink events);
void onCancel(Object arguments);
}

public interface EventSink {
void success(Object event);
void error(String errorCode, String errorMessage, Object errorDetails);
void endOfStream();
}

private final class IncomingStreamRequestHandler implements BinaryMessageHandler {
private final StreamHandler handler;
private final AtomicReference<EventSink> activeSink = new AtomicReference<>(null);

IncomingStreamRequestHandler(StreamHandler handler) {
this.handler = handler;
}

@Override
public void onMessage(ByteBuffer message, final BinaryReply reply) {
......
handler.onCancel();
......
handler.onListen();
......
}
}
}

name(Channel名称)

name是用于区分不同Platform Channel的唯一标志.在一个Flutter应用中,通常会存在多个Platform Channel,不同Channel之间通过name那么来区分.比如在使用MethodChannel平台发起方法调用时,需要为MethodChannel指定对应的name.

messager(信使)

messager也称为信使,通俗来说信使就是现代的快递员,它负责把数据从Flutter搬运到JAndroid/IOS平台,或者从Android/IOS搬运到Flutter).对于Flutter中的三种Channel,尽管各自用途不同,但messager都是BinaryMessager.

当我们创建一个Channel时,并为其设置消息处理器时,最终会为该Channel绑定一个BinaryMessagerHandler.并以Channel的name为key,保存在Map结构中.当接受到发送消息后,会根据消息中携带的channel名称取出对应BinaryMessagerHandler,并交由其处理.在Android平台中,BinaryMessenger是一个接口,其实现类是FlutterNativeView.在后续MethodChannel调用原理中,会进一步分析.

Codec(编解码器)

在Platform Channel中,messager携带的数据需要在Dart层,Native层以及Android/IOS平台中传输,需要考虑一种与平台无关的数据协议,且又能支持图片/文件等资源,因此官方最终采用了二进制字节流作为数据传输协议:发送方需要把数据编码成二进制数据,接受方再把数据解码成原始数据.而负责编解码操作的就是Codec.

在Flutter中有两种Codec:

  • MethodCodec: 用于对MethodCall编解码
  • MessageCodec: 用于对Message进行编解码

MessageCodec

MessageCodec用于二进制数据与基础数据之间的编解码,其中BasicMessageChannel中采用的就是该Codec.以Android平台为例,MessageCodec定义如下:

public interface MessageCodec<T> {
// 将指定的类型message编码为二进制数据ByteBuffer
ByteBuffer encodeMessage(T message);
// 将二进制数据ByteBuffer解码成指定类型
T decodeMessage(ByteBuffer message);
}

MessageCodec被设计为一个泛型接口,用于实现二进制数据ByteBuffer和不同类型数据之间的转换.(在IOS中,可参考FlutterMessageCodec协议,其原理基本一致).

在Flutter中,目前MessageCodec有多种实现:

image-20190209205941331

BinaryCodec

用于二进制数据和二进制数据之间的编解码,在实现上什么也没有做,只是原封不动的将二进制数据返回而已.

StringCodec

用于字符串与二进制数据之间的编解码,对于字符串采用UTF-8编码格式.

JSONMessageCodec

用于数据类型与二进制数据之间的编解码,支持基础数据类型(boolean,char,double,float,int,long,short,String)以及List,Map.在Android端使用JSONUtil和StringCodec作为序列化和反序列化工具.

StandardMessageCodec

用于数据类型和二进制数据之间的编解码,它也是BasicMessageChannel中默认使用的编解码器,支持基础数据类型(boolean,char,double,float,int,long,short,String),List,Map以及二进制数据,更多参见:深入编解码器原理

MethodCodec

MethodCodec用于二进制数据与方法调用(MethodCall)和返回结果之间的编解码.MethodChannel和EventChannel所使用的编解码器均为MethodCodec.以Android平台为例,MethodCodec定义如下:

public interface MethodCodec {
// 将methodCall编码为二进制ByteBuffer
ByteBuffer encodeMethodCall(MethodCall methodCall);

// 将二进制methodCall解码为MethodCall
MethodCall decodeMethodCall(ByteBuffer methodCall);

// 将正常响应结果result编码为二进制ByteBuffer
ByteBuffer encodeSuccessEnvelope(Object result);

// 将错误响应提示编码为二进制ByteBuffer
ByteBuffer encodeErrorEnvelope(String errorCode, String errorMessage, Object errorDetails);

// 将二进制数据ByteBuffer解码成Object
Object decodeEnvelope(ByteBuffer envelope);
}

一个MethodCall对象代表一次从Flutter端发起的方法调用,对于方法调用而言,涉及方法名,方法参数以及方法返回结果,因此和MessageCodec相比,MethodCodec中多了两个处理调用结果的方法:

  • 方法调用成功,使用encodeSuccessEnvelope()编码result
  • 方法调用失败,使用encodeErrorEnvelope()编码errorCode,errorMessage,errorDetail

decodeEnvelope()方法则用于解码平台代码调用Dart中方法的结果.比如Android平台通过MethodChannel调用Flutter中的方法,且获取其返回结果

在Flutter中,目前MethodCodec有两种实现:

image-20190209174504635

JSONMethodCodec

JSONMethodCodec编解码器依赖于JSONMessageCodec.在将MethodCall对象进行编码时,会首先将该对象转成JSON对象:{“method”:method,”args”:args},比如当前想要调用某个Channel的setVolum(5),其对应的MethodCall被被转成{"method":"setVolum","args":{"volum":5}},接下来使用JSONMessageCodec将其编码为二进制数据.

StandardMethodCodec

StandardMethodCodec的编解码器依赖于StandardMessageCodec,它也是MethodCodec的默认实现.当其编码在将MethodCall对象进行编码时,会将MethoCall对象的method和args依次使用StandardMessageCodec进行编码,然后写成二进制数据.

深入编解码器原理

在学习编写Platform Channel过程中,Flutter官方介绍了如何借助MethodChannel编写获取Android/IOS平台上的电量插件.在Android平台中电量返回值是java.lang.Integer类型,而IOS平台中电量返回值是NSNumber类型,而在Dart中,该返回值类型是dart语言中的int类型.下面以Android平台为例,通过分析StandardMethodCodec实现来了解其原理.

上面我们已经说过StandardMethodCodec中使用标准的二进制消息编码器StandardMessageCodec,首先来看一下StandardMethodCodec的定义:

public final class StandardMethodCodec implements MethodCodec {
public static final StandardMethodCodec INSTANCE = new StandardMethodCodec(StandardMessageCodec.INSTANCE);
private final StandardMessageCodec messageCodec;

public StandardMethodCodec(StandardMessageCodec messageCodec) {
this.messageCodec = messageCodec;
}

.......

}

现在重点分析StandardMessageCodec:

public class StandardMessageCodec implements MessageCodec<Object> {
public static final StandardMessageCodec INSTANCE = new StandardMessageCodec();

// 字节序判断,LITTLE_ENDIAN为true表示是小端模式,否则为大端模式.在对字节流写入和读取时,
// 需要根据字节序来决定读取和写入的顺序
private static final boolean LITTLE_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
private static final Charset UTF8 = Charset.forName("UTF8");
// 下面14个常量值用来标志不同类型的数据,比如0表示NULL,3表示int类型.在向字节流写入指定类型
// 的数据时,需要首先写入类型标志,然后紧跟着写入具体的值;在从字节流读取数据时,首先读取类型标
// 志,然后读取具体的数值.
private static final byte NULL = 0;
private static final byte TRUE = 1;
private static final byte FALSE = 2;
private static final byte INT = 3;
private static final byte LONG = 4;
private static final byte BIGINT = 5;
private static final byte DOUBLE = 6;
private static final byte STRING = 7;
private static final byte BYTE_ARRAY = 8;
private static final byte INT_ARRAY = 9;
private static final byte LONG_ARRAY = 10;
private static final byte DOUBLE_ARRAY = 11;
private static final byte LIST = 12;
private static final byte MAP = 13;


@Override
public ByteBuffer encodeMessage(Object message) {
if (message == null) {
return null;
}
final ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();
// 将message写入二进制字节流中
writeValue(stream, message);
final ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());
buffer.put(stream.buffer(), 0, stream.size());
return buffer;
}

@Override
public Object decodeMessage(ByteBuffer message) {
if (message == null) {
return null;
}
message.order(ByteOrder.nativeOrder());
// 从二进制字节流message中读取数据
final Object value = readValue(message);
if (message.hasRemaining()) {
throw new IllegalArgumentException("Message corrupted");
}
return value;
}

// 根据数据类型,先向stream中写入类型标志值,及上述提到的14个常量值,然后将具体的
// value值转成byte继续写入到stream
protected void writeValue(ByteArrayOutputStream stream, Object value) {
if (value == null) {
stream.write(NULL);
} else if (value == Boolean.TRUE) {
stream.write(TRUE);
} else if (value == Boolean.FALSE) {
stream.write(FALSE);
} else if (value instanceof Number) {
if (value instanceof Integer || value instanceof Short || value instanceof Byte) { // 1.写入类型标志值
stream.write(INT);
// value转为byte,继续写入到stream中
writeInt(stream, ((Number) value).intValue());
}
.......
}else if (value instanceof String) {
stream.write(STRING);
writeBytes(stream, ((String) value).getBytes(UTF8));
}
.......
}

// 根据字节序决定写入顺序
protected static final void writeInt(ByteArrayOutputStream stream, int value) {
if (LITTLE_ENDIAN) {
stream.write(value);
stream.write(value >>> 8);
stream.write(value >>> 16);
stream.write(value >>> 24);
} else {
stream.write(value >>> 24);
stream.write(value >>> 16);
stream.write(value >>> 8);
stream.write(value);
}
}

// writeValue()方法反向过程,原理一致
protected final Object readValue(ByteBuffer buffer) {
.......
}

static final class ExposedByteArrayOutputStream extends ByteArrayOutputStream {
byte[] buffer() {
return buf;
}
}
}

在StandardMessageCodec中最重要的两个方法是writeValue()readValue().前者用于将value值写入到字节输出流ByteArrayOutputStream中,后者从字节缓冲数组中读取.在Android返回电量的过程中,假设电量值为100,该值转换成二进制数据流程为:首先向字节流stream中写入表示int类型的标志值3,再将100转为4个byte,继续写入到字节流stream中.当Dart中接受到该二进制数据后,先读取第一个byte值,根据此值得知后面需要读取一个int类型的数据,随后读取后面4个byte,并将其转为dart类型中int类型.

image-20190209194123776

Handler

Flutter中定义了一套Handler用于处理经过Codec解码后消息.在使用Platform Channel时,需要为其设置对应的Handler,实际上就是为其注册一个对应BinaryMessageHandler,二进制数据会被BinaryMessageHanler进行处理,首先使用Codec进行解码操作,然后再分发给具体Handler进行处理.与三种Platform Channel相对应,Flutter中也定义了三种Handler:

  • MessageHandler: 用于处理字符串或者半结构化消息,定义在BasicMessageChannel中.
  • MethodCallHandler: 用于处理方法调用,定义在MethodChannel中.
  • StreamHandler: 用于事件流通信,定义在EventChannel中.

MessageHandler

用于处理字符串或者半结构化的消息,其定义如下:

public final class BasicMessageChannel<T> {
......

public interface MessageHandler<T> {
void onMessage(T message, Reply<T> reply);
}

......
}

onMessage()用于处理来自Flutter中的消息,该接受两个参数:T类型的消息以及用于异步返回T类型的result

MethodCallHandler

MethodCallHandler用于处理方法的调用,其定义如下:

public final class MethodChannel {
......

public interface MethodCallHandler {
void onMethodCall(MethodCall call, Result result);
}

......
}

onMessage()用于处理来自Flutter中消息,该方法接受两个参数:用于方法调用MethodCall对象以及用于方法返回Result类型的对象.

StreamHandler

StreamHandler用于事件流的通信,通常是用于平台主动向Flutter发送事件通知,其定义如下:

public final class EventChannel {
......

public interface StreamHandler {

void onListen(Object arguments, EventSink events);

void onCancel(Object arguments);
}

......
}

在StreamHandler存在两个方法:onListen()onCancel().当Flutter端开始监听平台事件时,会向平台发起一次MethodCall,其中方法名为listen,也就是最终会调用StreamHandler中的onListen()方法.该方法中接受两个参数,其中EventSink类型的参数可用于向Flutter发送事件(实际上还是通过BinaryMessager).当Flutter开始停止监听平台事件时,会再向平台发起一次MethodCall,其中方法名为cancel,最终会调用StreamHandler的onCancel()方法,在该方法中通常需要销毁一些无用的资源.关于StreamHandler原理,会另开一文.

MethodChannel调用原理

MethodChannel用于实现Flutter和Android/IOS平台间的方法调用.下文将以从Flutter调用Android平台代码为主线进行分析,主要涉及方法调用及方法调用结果返回两个过程.

方法调用

Dart -> Native

MethodChannel定义如下:

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时,需要我们根据Channel名称来创建MethodChannel对象.Channel名称作为MethodChannel的唯一标识符,用于区分不同的MethodChannel对象.创建MethodChannel对象的一般方式如下:

final MethodChannel _channel = new MethodChannel('flutter.io/player')

拿到MethodChannel对象后,通过调用其invokeMethod()方法用于向平台发起一次调用.在invokeMethod()方法中会将一次方法调中的方法名method和方法参数arguments封装为MethodCall对象,然后使用MethodCodec对其进行二进制编码,最后通过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;
}
......
}

BinaryMessages类中提供了用于发送和接受平台插件的二进制消息.

class Window{
......
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) {
final String error =
_sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
if (error != null)
throw new Exception(error);
}
// 和Java类似,Dart中同样提供了Native方法用于调用底层C++/C代码的能力
String _sendPlatformMessage(String name,
PlatformMessageResponseCallback callback,
ByteData data) native 'Window_sendPlatformMessage';
.......
}

上述过程最终会调用到ui.Window的_sendPlatformMessage()方法,该方法是一个native方法,这与Java中JNI技术非常类似.

image-20190209112548889

在调用该Native方法中,我们向native层发送了三个参数:

  • name: String类型,代表Channel名称
  • data: ByteData类型,代表之前封装的二进制数据
  • callback: Function类型,用于结果回调

_sendPlatformMessage()具体实现在Window.cc中:

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},
{"Window_reportUnhandledException", ReportUnhandledException, 2, true},
});
}
void _SendPlatformMessage(Dart_NativeArguments args) {
// 最终调用SendPlatformMessage函数
tonic::DartCallStatic(&SendPlatformMessage, args);
}

SendPlatformMessage()函数定义如下:

Dart_Handle SendPlatformMessage(Dart_Handle window,
const std::string& name,
Dart_Handle callback,
const tonic::DartByteData& data) {
UIDartState* dart_state = UIDartState::Current();
// 1.只能在main iolate调用平台方法
if (!dart_state->window()) {
// Must release the TypedData buffer before allocating other Dart objects.
data.Release();
return tonic::ToDart(
"Platform messages can only be sent from the main isolate");
}
// 此处response的作用?
fml::RefPtr<PlatformMessageResponse> response;
if (!Dart_IsNull(callback)) {
response = fml::MakeRefCounted<PlatformMessageResponseDart>(
tonic::DartPersistentValue(dart_state, callback),
dart_state->GetTaskRunners().GetUITaskRunner());
}
// 2.核心方法调用
if (Dart_IsNull(data.dart_handle())) {
dart_state->window()->client()->HandlePlatformMessage(
fml::MakeRefCounted<PlatformMessage>(name, response));
} else {
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();
}

在上方代码中首先判断是否在main isolate中进行平台方法调用,如果不是返回错误信息;接下来就是执行关键的方法:dart_state->window()->client()->HandlePlatformMessage().其中HandlePlatformMessage()WindowClient中虚方法:

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();
};

RuntimeController是WindowClient中的唯一继承类,其头文件 runtime_controller.h,为了方便起见,我们只看其部分定义:

class RuntimeController final : public WindowClient {
......

private:
RuntimeDelegate& client_;

.......
void HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) override;
......

}

知道其定义之后,现在就可以看起具体在runtime_controller.cc的实现了:

void RuntimeController::HandlePlatformMessage(
fml::RefPtr<PlatformMessage> message) {
client_.HandlePlatformMessage(std::move(message));
}

在运行过程中,不同的平台有运行机制不同,需要不同的处理策略,因此RuntimeController中相关的方法实现都被委托到了不同的平台实现类RuntimeDelegate中,即上述代码中client_.

RuntimeDelegate的实现为Engine,其头文件Engine.h定义如下:

class Engine final : public blink::RuntimeDelegate {
........
}

对应真实的实现在Engine.cc中,直接来看其HandlePlatformMessage()的实现

void Engine::HandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message) {
// kAssetChannel值为flutter/assets
if (message->channel() == kAssetChannel) {
HandleAssetPlatformMessage(std::move(message));
} else {
delegate_.OnEngineHandlePlatformMessage(std::move(message));
}
}

在上述代码过程中,Engine在处理message时,如果该message值等于kAssetChannel,即flutter/assets,表示当前操作想要获取资源,因此会调用HandleAssetPlatformMessage()来走获取资源的逻辑;否则调用delegate_.OnEngineHandlePlatformMessage()方法.这里delegate的具体实现为 Shell:

void Shell::OnEngineHandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message) {
FML_DCHECK(is_setup_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());

// kSkiaChannel值为flutter/skia
if (message->channel() == kSkiaChannel) {
HandleEngineSkiaMessage(std::move(message));
return;
}
// 其他情况下,向PlatformTaskRunner中添加Task
task_runners_.GetPlatformTaskRunner()->PostTask(
[view = platform_view_->GetWeakPtr(), message = std::move(message)]() {
if (view) {
view->HandlePlatformMessage(std::move(message));
}
});
}

OnEngineHandlePlatformMessage在接收到消息后,首先判断要调用Channel是否是flutter/skia,如果是则调用HandleEngineSkiaMessage()进行处理后返回,否则向PlatformTaskRunner添加一个Task,在该Task中会调用PlatformView的HandlePlatformMessage()方法.根据运行平台不同PlatformView有不同的实现,对于Android平台而言,其具体实现是PlatformViewAndroid;对于IOS平台而言,其实现是PlatformViewIOS.

image-20190208112830510

Task中的代码执行在Platform Task Runner中,而之前的代码运行在UI Task Runner中

以PlatformViewAndroid为例,来了解HandlePlatformMessage()方法的实现:

// |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;
// response_id在Flutter调用平台代码时,会传到平台代码中,后续平台代码需要回传数据时
// 需要用到它
int response_id = 0;
// 如果message中有response(response类型为PlatformMessageResponseDart),则需要对
// response_id进行自增
if (auto response = message->response()) {
response_id = next_response_id_++;
// pending_responses是一个Map结构
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 in InvokePlatformMessageXxxResponseCallback.
FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),
nullptr, response_id);
}
}

在上述代码中,当该方法接受PlatformMessage类型的消息时,如果消息中有response,则对response_id自增,并以response_ide为key,response为value存放在变量pending_responses_中(pending_responsed是一个Map结构).

正常情况下,每次从Flutter调用Channel代码,都会生成对应的response_id和response.同时该response_id会被传到平台代码中,当平台代码需要为此次调用返回数据时,需要同时回传该response_id.

接着将消息中的channel和data数据转成Java可识别的数据,并连同response_id一同作为FlutterViewHandlePlatformMessage()方法的参数,最终通过JNI调用的方式传递到Java层.

简单的分析下该过程,首先来看FlutterViewHandlePlatformMessage()platform_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));
}

在上述方法中,最终将调用g_handle_platform_message_method中指向Java层的方法.其中g_handle_platform_message_method在RegisterApi中被初始化:

bool PlatformViewAndroid::Register(JNIEnv* env) {
......
g_flutter_jni_class = new fml::jni::ScopedJavaGlobalRef<jclass>(
env, env->FindClass("io/flutter/embedding/engine/FlutterJNI"));

......

return RegisterApi(env);
}

bool RegisterApi(JNIEnv* env) {
......
g_handle_platform_message_method =
env->GetMethodID(g_flutter_jni_class->obj(), "handlePlatformMessage",
"(Ljava/lang/String;[BI)V");
......
}

不难看出g_flutter_jni_class指向FlutterJNI.java类,g_handle_platform_message_method指向FlutterJN.javaI中的handlePlatformMessage()方法.

现在我们知道从Flutter发一次Channel调用,需要经过Dart -> Native -> JNI -> Java几个层次,并把三个参数channel,message,responseId传给Java层.

image-20190209113644507

Native -> Java

通过上述分析,我们知道FlutterViewHandlePlatformMessage()实际上是通过JNI的方式最终调用了FlutterJNI.java中的handlePlatformMessage()方法,该方法接受三个来自Native层的参数:

  • channel: String类型,表示Channel名称.
  • message: 字节数组,表示方法调用中的数据,如方法名和参数.
  • replyId: int类型,在将此次调用的响应数据从Java层写回到Native层时用到
public class FlutterJNI {
private PlatformMessageHandler platformMessageHandler;

@UiThread
public void setPlatformMessageHandler(@Nullable PlatformMessageHandler platformMessageHandler) {
this.platformMessageHandler = platformMessageHandler;
}

// Called by native.
@SuppressWarnings("unused")
private void handlePlatformMessage(final String channel, byte[] message, final int replyId) {
if (platformMessageHandler != null) {
platformMessageHandler.handleMessageFromDart(channel, message, replyId);
}
}
}

FlutterJNI类定义了Java层和Flutter C/C++引擎之间的相关接口.此类目前处于实验性质,随着后续的发展可能会被不断的重构和优化,不保证一直存在,不建议开发者调用该类.

为了建立Android应用和Flutter C/C++引擎的连接,需要创建FlutterJNI实例,然后将其attach到Native,常见的使用方法如下:

// 1.创建FlutterJNI实例
FlutterJNI flutterJNI = new FlutterJNI();
// 2.建立和Native层的连接
flutterJNI.attachToNative();

......

// 3.断开和Native层的连接,并释放资源
flutterJNI.detachFromNativeAndReleaseResources();

重新回到FlutterJNI中handlePlatformMessage(),在该方法中首先判断platformMessageHandler是否为null,不为null,则调用其handleMessageFromDart()方法.其中platformMessageHandler需要通过FlutterJNI中的setPlatformMessageHandler()方法来设置.那该方法被调用的时机是在什么时候呢?直接来看FlutterNativeView的 构造函数:

public class FlutterNativeView implements BinaryMessenger {

private final Map<String, BinaryMessageHandler> mMessageHandlers;
private int mNextReplyId = 1;
private final Map<Integer, BinaryReply> mPendingReplies = new HashMap<>();

private final FlutterPluginRegistry mPluginRegistry;
private FlutterView mFlutterView;
private FlutterJNI mFlutterJNI;
private final Context mContext;
private boolean applicationIsRunning;

public FlutterNativeView(Context context, boolean isBackgroundView) {
mContext = context;
mPluginRegistry = new FlutterPluginRegistry(this, context);
// 创建FlutterJNI实例
mFlutterJNI = new FlutterJNI();
mFlutterJNI.setRenderSurface(new RenderSurfaceImpl());
// 将PlatformMessageHandlerImpl实例赋值给FlutterJNI中的platformMessageHandler属性
mFlutterJNI.setPlatformMessageHandler(new PlatformMessageHandlerImpl());
mFlutterJNI.addEngineLifecycleListener(new EngineLifecycleListenerImpl());
attach(this, isBackgroundView);
assertAttached();
mMessageHandlers = new HashMap<>();
}

.......
}

在FlutterNativeView的构造函数中,首先创建FlutterJNI实例mFlutterJNI,然后调用setPlatformMessageHandler()并把PlatformMessageHandlerImpl实例作为参数传入.因此在FlutterJNI的handlePlatformMessage()方法中,最终调用PlatformMessageHandlerImpl实例的handleMessageFromDart()来处理来自Flutter中的消息:

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);
}
}

在FlutterNativeView中存在Map类型的成员变量mMessageHandler:以Channel名称作为key,以BinaryMessageHandler类型为value.在handleMessageFromDart()方法中,首先根据Channel名称从mMessageHandlers取出对应的二进制消息处理器BinaryMessageHandler,然后将字节数组message封装为ByteBuffer对象,然后调用BinaryMessageHandler实例的onMessage()方法处理ByteBuffer,并进行响应.

BinaryReply是一个接口,主要用来将ByteBuffer类型的响应数据reply从Java层写回到Flutter层.根据reply是否为null,调用FlutterJNI实例不同的方法:reply为null时,调用invokePlatformMessageEmptyResponseCallback();否则调用invokePlatformMessageResponseCallback().关于具体的实现流程,稍后再述.

现在来看下BinaryMessageHandler是如何添加到mMessageHandler中:

public class FlutterNativeView implements BinaryMessenger {
private final Map<String, BinaryMessageHandler> mMessageHandlers;

......

@Override
public void setMessageHandler(String channel, BinaryMessageHandler handler) {
if (handler == null) {
mMessageHandlers.remove(channel);
} else {
mMessageHandlers.put(channel, handler);
}
}
.......
}

setMessageHandler()方法接受两个参数:channel作为Channel的名称,用来区分不同的Channel;handler是该Channel对应的二进制消息处理器.在该方法中,会根据handler是否为null来决定对mMessageHandlers是进行添加还是删除操作.那该方法什么时候回被调用呢?要想弄明白这个问题,需要了解编写平台Channel的过程.以官方获取Android平台电量的平台Channel为例:

public class MainActivity extends FlutterActivity {
// 1.定义Channel的名称,该名称作为Channel的唯一标识符
private static final String CHANNEL = "samples.flutter.io/battery";

@Override
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
// 2.创建MethodChannel对象channel
MethodChannel channel = new MethodChannel(getFlutterView(), CHANNEL);
// 3.调用MethodChannel实例的setMethodCallHandler()方法为当前channel设置Handler
channel.setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
// TODO
}
});
}
}

在上述代码中,演示了编写平台代码的基本过程:首先创建MethodChanel实例,然后设置MethodCallHandler.

public final class MethodChannel {
// 二进制信使
private final BinaryMessenger messenger;
// Channel名称
private final String name;
// 方法编码
private final MethodCodec codec;

public MethodChannel(BinaryMessenger messenger, String name) {
this(messenger, name, StandardMethodCodec.INSTANCE);
}

public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
assert messenger != null;
assert name != null;
assert codec != null;
this.messenger = messenger;
this.name = name;
this.codec = codec;
}

......

public void setMethodCallHandler(final @Nullable MethodCallHandler handler) {
messenger.setMessageHandler(name,
handler == null ? null : new IncomingMethodCallHandler(handler));
}
......
}

在创建MethodChannel过程中需要指定三个参数:

  • name: Channel名称,用作唯一标识符.
  • codec: 用于方法编码的MethodCodec,.默认情况下,codec被指定为StandartMethodCodec.INSTANCE.
  • messager: 用于消息发送的BinaryMessager,

创建完MethodChannel后,接下来需要调用setMethodCallHandler()设置用于处理方法调用MethodCallHandler.在该方法参数handler不为null的情况下,会将该handler包装为IncomingMethodCallHandler实例,然后调用BinaryMessager实例的setMessageHanlder()方法将I该ncomingMethodCallHandler实例保存在FlutterNativeView中的mMessageHandlers中.

简单来说就是在编写平台Channel时,需要创建对应的MethodChannel实例,并调用其setMethodCallHandler()将MethodCallHandler实例保存起来.

public final class MethodChannel {

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()方法.

image-20190209115925942

.MethodCall中包含两部分数据:method部分表示要调用的方法;arguments表示被调用方法所需要的参数.

public final class MethodChannel {
.......
public interface MethodCallHandler {
void onMethodCall(MethodCall call, Result result);
}
.......
}

接下来便是调用自定义MethodCallHandler中的onMethodCall()方法,该方法接受两个参数:

  • call: MethodCall类型,它包含方法调用所需的信息

  • result: Result类型,用于处理方法调用结果

Result是一个回调接口,其定义如下:

public final class MethodChannel {
.......
public interface Result {
// 方法调用处理成功时,调用success()
void success(@Nullable Object result);
// 方法调用处理失败时,调用error()
void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails);
// 方法调用遇到为定义的方法时,调用notImplemented()
void notImplemented();
}
.......
}

方法返回

Java -> Native

回到IncomingMethodCallHandler中的onMessage()中,我们看到在调用MethodCallHandler的onMethodCall()时,以匿名内部的形式实现了Result接口,而且在实现中又调用reply实例的reply()方法来把响应数据写会到Flutter层.reply是BinaryReply接口类型,其具体实现之前已经说过(即 PlatformMessageHandlerImpl,在此就不做重复了.

当数据需要写回时,数据首先通过codec被编码成ByteBuffer类型,然后调用reply的reply()方法.在reply()方法中,对于非null类型的ByteBuffer,会调用FlutterJNI中的invokePlatformMessageResponseCallback()

public class FlutterJNI {
private Long nativePlatformViewId;

......
@UiThread
public void invokePlatformMessageResponseCallback(int responseId, ByteBuffer message, int position) {
// 1.检查FlutterJNI是否已经attach到Native层,如若没有则抛出异常
ensureAttachedToNative();
// 2.继续调用nativeInvokePlatformMessageResponseCallback()
nativeInvokePlatformMessageResponseCallback(
nativePlatformViewId,
responseId,
message,
position
);
}

private native void nativeInvokePlatformMessageResponseCallback(
long nativePlatformViewId,
int responseId,
ByteBuffer message,
int position
);

......

private void ensureAttachedToNative() {
// FlutterJNI attach到Native层后,会返回一个long类型的值用来初始化nativePlatformViewId
if (nativePlatformViewId == null) {
throw new RuntimeException("Cannot execute operation because FlutterJNI is not attached to native.");
}
}

}

在上述invokePlatformMessageResponseCallback()方法中,首先检查当前FlutterJNI实例是否已经attach到Native层,然后调用Native方法nativeInvokePlatformMessageResponseCallback()向JNI层写入数据,该Native方法Native层的实现在platform_view_android.cc,其中参数responseId是之前Native层生成,又传至Java层的,现在有又需要将它传至Native层:

bool RegisterApi(JNIEnv* env) {
static const JNINativeMethod flutter_jni_methods[] = {
......
{
.name = "nativeInvokePlatformMessageResponseCallback",
.signature = "(JILjava/nio/ByteBuffer;I)V",
.fnPtr = reinterpret_cast<void*>(&shell::InvokePlatformMessageResponseCallback),
},
.......
}
.......
}

通过上述代码定义不难看出其最终实现如下:

void PlatformViewAndroid::InvokePlatformMessageResponseCallback(
JNIEnv* env,
jint response_id,
jobject java_response_data,
jint java_response_position) {
if (!response_id)
return;
// 1.通过response_id从pending_responses_中取出response
auto it = pending_responses_.find(response_id);
if (it == pending_responses_.end())
return;
// 2.GetDirectBufferAddress函数返回一个指向被传入的ByteBuffer对象的地址指针
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);
// 3.从pending_responses_中移除该response
pending_responses_.erase(it);
// 4.调用response的Complete()方法将二进制结果返回
message_response->Complete(
std::make_unique<fml::DataMapping>(std::move(response)));
}

在上述代码中,首先以response_id为key,从pending_responsed_取出对应response.然后通过GetDirectBufferAddress函数获取二进制响应数据java_response_data对象的指针,最后调用reponse的Complete()方法将二进制结果返回.

Native -> Dart

上文提到response是PlatformMessageResponseDart类型,简单看一下其Complete()方法实现:

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);
// 将Native层的二进制数据data转为Dart中的二进制数据byte_buffer
Dart_Handle byte_buffer = WrapByteData(std::move(data));
tonic::DartInvoke(callback.Release(), {byte_buffer});
}));
}

在上述代码中,向ui_task_runner_添加了一个新的Task,在该Task中首先将Native层二进制数据转为Dart中的二进制数据,然后调用Dart中的callback将数据返回到Dart层.Dart层在接受到数据后,使用MethodCodec进行解码数据并将其返回到业务代码.

小结

上述以Dart作为发起方,完整梳理一次从Dart向Android平台发起方法调用(MethodCall)流程.实际MethodCall也支持以Android平台为发起方,通过MethodChannel调用Dart中的方法,对此就不做说明了.=

总结

目前为止,关于Platform Channel基本原理已经讲解完成,最后通过对MethodChannel工作原理进行分析来加深理解.对于IOS开发者而言,其原理实现类似.

总体而言,Flutter中实现跨平台通信的机制简单却高效,总体难度不大.最后祝大家新年快乐,人人都能拐卖小学妹😈(Flutter).

原文作者:lionoggo

原文链接:yoursite.com/2019/02/09/…

发表日期:February 9th 2019, 9:20:50 pm

更新日期:February 9th 2019, 10:24:25 pm

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可