Flutter是目前非常流行的跨平台方案,由于它的性能接近于原生应用,因而被越来越多的开发者所采用。既然是跨平台方案,那么久必然存在调用系统功能的需求,在Flutter中,Flutter层与native层的互调,是通过MethodChannel来实现的。下面来简单的分析下Android端调用Flutter的过程。
MethodChannel channel = new MethodChannel(messenger, CHANNEL_NAME , StandardMethodCodec.INSTANCE);
///构造函数
public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec)
上面创建了一个MethodChannel,从构造函数中可以看到有三个参数, messenger,name,codec.
其中messenger的参数类型是BinaryMessenger, MethodChannel就是通过BinaryMessenger来实现和Flutter层的交互,BinaryMessenger存在期间,是在单线程中执行的,如果在主线程中创建,那么就在主线程中执行,如果是在子线程中创建的,则在主线程中执行。name指定了渠道的名字,应用中可以存在多个渠道,这个需要与Flutter层中的渠道名称一致。codec指定了信息编解码的方式, codec的类型是MethodCodec,它有两种实现:JsonMethodCodec 、StandardMethodCodec。这两种方式的区别在于编解码的方式不一样,但最终都会把消息转换成ByteBuffer。我们分别看下这种方式的编码方式:
JSONUtils.java
/** Backport of {@link JSONObject#wrap(Object)} for use on pre-KitKat systems. */
public static Object wrap(Object o) {
if (o == null) {
return JSONObject.NULL;
}
if (o instanceof JSONArray || o instanceof JSONObject) {
return o;
}
if (o.equals(JSONObject.NULL)) {
return o;
}
try {
if (o instanceof Collection) {
JSONArray result = new JSONArray();
for (Object e : (Collection) o) result.put(wrap(e));
return result;
} else if (o.getClass().isArray()) {
JSONArray result = new JSONArray();
int length = Array.getLength(o);
for (int i = 0; i < length; i++) result.put(wrap(Array.get(o, i)));
return result;
}
if (o instanceof Map) {
JSONObject result = new JSONObject();
for (Map.Entry<?, ?> entry : ((Map<?, ?>) o).entrySet())
result.put((String) entry.getKey(), wrap(entry.getValue()));
return result;
}
if (o instanceof Boolean
|| o instanceof Byte
|| o instanceof Character
|| o instanceof Double
|| o instanceof Float
|| o instanceof Integer
|| o instanceof Long
|| o instanceof Short
|| o instanceof String) {
return o;
}
if (o.getClass().getPackage().getName().startsWith("java.")) {
return o.toString();
}
} catch (Exception ignored) {
}
return null;
}
@Override
public ByteBuffer encodeMessage(Object message) {
if (message == null) {
return null;
}
final Object wrapped = JSONUtil.wrap(message);
if (wrapped instanceof String) {
return StringCodec.INSTANCE.encodeMessage(JSONObject.quote((String) wrapped));
} else {
return StringCodec.INSTANCE.encodeMessage(wrapped.toString());
}
}
@Override
public ByteBuffer encodeMessage(String message) {
if (message == null) {
return null;
}
// TODO(mravn): Avoid the extra copy below.
final byte[] bytes = message.getBytes(UTF8);
final ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);
buffer.put(bytes);
return buffer;
}
可以很清楚的看到,JSONMethodCodec会把数据包装成Json的格式,最终装换成ByteBuffer,同时可以看到只支持基本数据类型及相关集合。
/**
* Writes a type discriminator byte and then a byte serialization of the specified value to the
* specified stream.
*
* <p>Subclasses can extend the codec by overriding this method, calling super for values that the
* extension does not handle.
*/
protected void writeValue(ByteArrayOutputStream stream, Object value) {
if (value == null || value.equals(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) {
stream.write(INT);
writeInt(stream, ((Number) value).intValue());
} else if (value instanceof Long) {
stream.write(LONG);
writeLong(stream, (long) value);
} else if (value instanceof Float || value instanceof Double) {
stream.write(DOUBLE);
writeAlignment(stream, 8);
writeDouble(stream, ((Number) value).doubleValue());
} else if (value instanceof BigInteger) {
stream.write(BIGINT);
writeBytes(stream, ((BigInteger) value).toString(16).getBytes(UTF8));
} else {
throw new IllegalArgumentException("Unsupported Number type: " + value.getClass());
}
} else if (value instanceof String) {
stream.write(STRING);
writeBytes(stream, ((String) value).getBytes(UTF8));
} else if (value instanceof byte[]) {
stream.write(BYTE_ARRAY);
writeBytes(stream, (byte[]) value);
} else if (value instanceof int[]) {
stream.write(INT_ARRAY);
final int[] array = (int[]) value;
writeSize(stream, array.length);
writeAlignment(stream, 4);
for (final int n : array) {
writeInt(stream, n);
}
} else if (value instanceof long[]) {
stream.write(LONG_ARRAY);
final long[] array = (long[]) value;
writeSize(stream, array.length);
writeAlignment(stream, 8);
for (final long n : array) {
writeLong(stream, n);
}
} else if (value instanceof double[]) {
stream.write(DOUBLE_ARRAY);
final double[] array = (double[]) value;
writeSize(stream, array.length);
writeAlignment(stream, 8);
for (final double d : array) {
writeDouble(stream, d);
}
} else if (value instanceof List) {
stream.write(LIST);
final List<?> list = (List) value;
writeSize(stream, list.size());
for (final Object o : list) {
writeValue(stream, o);
}
} else if (value instanceof Map) {
stream.write(MAP);
final Map<?, ?> map = (Map) value;
writeSize(stream, map.size());
for (final Entry<?, ?> entry : map.entrySet()) {
writeValue(stream, entry.getKey());
writeValue(stream, entry.getValue());
}
} else {
throw new IllegalArgumentException("Unsupported value: " + value);
}
}
同样的,StandardMethodCodec同样是返回的是ByteBuffer,也是仅支持基本数据类型及相关集合。解码方式刚好相反,再此就不列出来了。
@UiThread
public void invokeMethod(String method, @Nullable Object arguments, @Nullable Result callback) {
messenger.send(
name,
codec.encodeMethodCall(new MethodCall(method, arguments)),
callback == null ? null : new IncomingResultHandler(callback));
}
MethodChannel的invokeMethod方法,触发了与Flutter的交互,可以看到,通过MethodCodec编码之后,通过BinaryMessenger的send方法发送数据给flutter层:
@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);
}
}
/** Sends a reply {@code message} from Android to Flutter over the given {@code channel}. */
@UiThread
public void dispatchPlatformMessage(
@NonNull String channel, @Nullable ByteBuffer message, int position, int responseId) {
ensureRunningOnMainThread();
if (isAttached()) {
nativeDispatchPlatformMessage(nativePlatformViewId, channel, message, position, responseId);
} else {
Log.w(
TAG,
"Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: "
+ channel
+ ". Response ID: "
+ responseId);
}
}
// Send a data-carrying platform message to Dart.
private native void nativeDispatchPlatformMessage(
long nativePlatformViewId,
@NonNull String channel,
@Nullable ByteBuffer message,
int position,
int responseId);
从截取的代码中可以看到最终是通过jni来完成Flutter和Native的交互的,另外invokeMethod方法需要在UI线程中执行,否则会跑出异常:
private void ensureRunningOnMainThread() {
if (Looper.myLooper() != mainLooper) {
throw new RuntimeException(
"Methods marked with @UiThread must be executed on the main thread. Current thread: "
+ Thread.currentThread().getName());
}
}
上面就是MethodChannel发送消息的流程,那么是如何接受消息的呢。上面涉及到的BinaryMessenger有个内部接口:
/** Handler for incoming binary messages from Flutter. */
interface BinaryMessageHandler {
/**
* Handles the specified message.
*
* <p>Handler implementations must reply to all incoming messages, by submitting a single reply
* message to the given {@link BinaryReply}. Failure to do so will result in lingering Flutter
* reply handlers. The reply may be submitted asynchronously.
*
* <p>Any uncaught exception thrown by this method will be caught by the messenger
* implementation and logged, and a null reply message will be sent back to Flutter.
*
* @param message the message {@link ByteBuffer} payload, possibly null.
* @param reply A {@link BinaryReply} used for submitting a reply back to Flutter.
*/
@UiThread
void onMessage(@Nullable ByteBuffer message, @NonNull BinaryReply reply);
}
从注释中可以看到,处理消息是在onMessagel来完成,而且必须对所有接受到的消息要有回应,如果没有这么做会导致Flutter需要回应的地方始终收不到回应,从而使Flutter层停留在某个状态,消息可能为空,BinaruReply用于回应Flutter层。onMessage在收到Flutter消息的时候被调用,可以看到onMessage是在UI线程中执行的.
@Override
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.");
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);
}
}
上面就是onMessage调用的地方。那么我们常用的MethodChannel的onMethodCall是在哪里调用的。上面说到了,接收到Flutter的消息是在BinaryMessageHandler的onMessage方法中处理的,而MethodChannel的内部类正好IncomingMethodCallHandler实现这个方法:
private final class IncomingMethodCallHandler implements BinaryMessageHandler {
private final MethodCallHandler handler;
IncomingMethodCallHandler(MethodCallHandler handler) {
this.handler = handler;
}
@Override
@UiThread
public void onMessage(ByteBuffer message, final BinaryReply reply) {
final MethodCall call = codec.decodeMethodCall(message);
try {
handler.onMethodCall(
call,
new Result() {
@Override
public void success(Object result) {
reply.reply(codec.encodeSuccessEnvelope(result));
}
@Override
public void error(String errorCode, String errorMessage, Object errorDetails) {
reply.reply(codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
@Override
public void notImplemented() {
reply.reply(null);
}
});
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to handle method call", e);
reply.reply(
codec.encodeErrorEnvelopeWithStacktrace(
"error", e.getMessage(), null, getStackTrace(e)));
}
}
private String getStackTrace(Exception e) {
Writer result = new StringWriter();
e.printStackTrace(new PrintWriter(result));
return result.toString();
}
}
前面讲到,Native接收到消息后,需要给Flutter回复消息,在上面截取的代码中其实已经有这个实现了:
new Result() {
@Override
public void success(Object result) {
reply.reply(codec.encodeSuccessEnvelope(result));
}
@Override
public void error(String errorCode, String errorMessage, Object errorDetails) {
reply.reply(codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
@Override
public void notImplemented() {
reply.reply(null);
}
}
@Override
public void reply(@Nullable ByteBuffer reply) {
if (done.getAndSet(true)) {
throw new IllegalStateException("Reply already submitted");
}
if (reply == null) {
flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
} else {
flutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position());
}
}
@UiThread
public void invokePlatformMessageResponseCallback(
int responseId, @Nullable ByteBuffer message, int position) {
ensureRunningOnMainThread();
if (isAttached()) {
nativeInvokePlatformMessageResponseCallback(
nativePlatformViewId, responseId, message, position);
} else {
Log.w(
TAG,
"Tried to send a platform message response, but FlutterJNI was detached from native C++. Could not send. Response ID: "
+ responseId);
}
}
// Send a data-carrying response to a platform message received from Dart.
private native void nativeInvokePlatformMessageResponseCallback(
long nativePlatformViewId, int responseId, @Nullable ByteBuffer message, int position);