第二也是最关键的一点就是那些文章基本上是一气呵成的要不就是作者没有设计到更深入的层次去分析,而是点到而止就“打道回府”了,要么就是水平很高但是内容不够详细丰富,特别是不够通俗易懂的去给讲解,导致大家看了他们写的文章以后有一种“失落”的感觉,觉得大致都能看懂但就是理解不了什么意思,于是就有了我们今天这篇文章,看我能不能带着大家 通·俗·易·懂· 的去理解一下这个原理的过程,我保证绝对会让大家看完以后长舒一口气感叹:“原来原理就是这么的‘简单’啊”,希望大家在末尾给我评论留言,every body let’s go
Channel name 一个Flutter应用中可能存在多个不同的Channel,每个Channel在创建时必须指定一个独一无二的name,Channel之间使用name来区分彼此,当有消息从Flutter端发送到Native端时会根据其传递过来的channel name找到该Channel对应的Handler(消息处理器)
/// Sets a callback for receiving messages from the platform plugins on this
/// channel. Messages may be null.
///
/// The given callback will replace the currently registered callback for this
/// channel, if any. To remove the handler, pass null as the `handler`
/// argument.
///
/// The handler's return value is sent back to the platform plugins as a
/// message reply. It may be null.
void setMessageHandler(Future Function(T? message)? handler) {
if (handler == null) {
binaryMessenger.setMessageHandler(name, null);
} else {
binaryMessenger.setMessageHandler(name, (ByteData? message) async {
return codec.encodeMessage(await handler(codec.decodeMessage(message)));
});
}
}
public final class BinaryCodec implements MessageCodec {
// This codec must match the Dart codec of the same name in package flutter/services.
public static final BinaryCodec INSTANCE = new BinaryCodec();
private BinaryCodec() {}
@Override
public ByteBuffer encodeMessage(ByteBuffer message) {
return message;
}
@Override
public ByteBuffer decodeMessage(ByteBuffer message) {
return message;
}
}
Dart部分
class BinaryCodec implements MessageCodec {
/// Creates a [MessageCodec] with unencoded binary messages represented using
/// [ByteData].
const BinaryCodec();
@override
ByteData? decodeMessage(ByteData? message) => message;
@override
ByteData? encodeMessage(ByteData? message) => message;
}
class StringCodec implements MessageCodec {
/// Creates a [MessageCodec] with UTF-8 encoded String messages.
const StringCodec();
@override
String? decodeMessage(ByteData? message) {
if (message == null)
return null;
return utf8.decoder.convert(message.buffer.asUint8List(message.offsetInBytes, message.lengthInBytes));
}
@override
ByteData? encodeMessage(String? message) {
if (message == null)
return null;
final Uint8List encoded = utf8.encoder.convert(message);
return encoded.buffer.asByteData();
}
}
Android部分
public final class StringCodec implements MessageCodec {
private static final Charset UTF8 = Charset.forName("UTF8");
public static final StringCodec INSTANCE = new StringCodec();
private StringCodec() {}
@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;
}
@Override
public String decodeMessage(ByteBuffer message) {
if (message == null) {
return null;
}
final byte[] bytes;
final int offset;
final int length = message.remaining();
if (message.hasArray()) {
bytes = message.array();
offset = message.arrayOffset();
} else {
// TODO(mravn): Avoid the extra copy below.
bytes = new byte[length];
message.get(bytes);
offset = 0;
}
return new String(bytes, offset, length, UTF8);
}
}
void writeValue(WriteBuffer buffer, Object? value) {
if (value == null) {
buffer.putUint8(_valueNull);
} else if (value is bool) {
buffer.putUint8(value ? _valueTrue : _valueFalse);
} else if (value is double) { // Double precedes int because in JS everything is a double.
// Therefore in JS, both `is int` and `is double` always
// return `true`. If we check int first, we'll end up treating
// all numbers as ints and attempt the int32/int64 conversion,
// which is wrong. This precedence rule is irrelevant when
// decoding because we use tags to detect the type of value.
buffer.putUint8(_valueFloat64);
buffer.putFloat64(value);
} else if (value is int) {
if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) {
buffer.putUint8(_valueInt32);
buffer.putInt32(value);
} else {
buffer.putUint8(_valueInt64);
buffer.putInt64(value);
}
} else if (value is String) {
buffer.putUint8(_valueString);
final Uint8List bytes = utf8.encoder.convert(value);
writeSize(buffer, bytes.length);
buffer.putUint8List(bytes);
} else if (value is Uint8List) {
buffer.putUint8(_valueUint8List);
writeSize(buffer, value.length);
buffer.putUint8List(value);
} else if (value is Int32List) {
buffer.putUint8(_valueInt32List);
writeSize(buffer, value.length);
buffer.putInt32List(value);
} else if (value is Int64List) {
buffer.putUint8(_valueInt64List);
writeSize(buffer, value.length);
buffer.putInt64List(value);
} else if (value is Float64List) {
buffer.putUint8(_valueFloat64List);
writeSize(buffer, value.length);
buffer.putFloat64List(value);
} else if (value is List) {
buffer.putUint8(_valueList);
writeSize(buffer, value.length);
for (final Object? item in value) {
writeValue(buffer, item);
}
} else if (value is Map) {
buffer.putUint8(_valueMap);
writeSize(buffer, value.length);
value.forEach((Object? key, Object? value) {
writeValue(buffer, key);
writeValue(buffer, value);
});
} else {
throw ArgumentError.value(value);
}
}
Object? readValue(ReadBuffer buffer) {
if (!buffer.hasRemaining)
throw const FormatException('Message corrupted');
final int type = buffer.getUint8();
return readValueOfType(type, buffer);
}
/// Reads a value of the indicated [type] from [buffer].
///
/// The codec can be extended by overriding this method, calling super for
/// types that the extension does not handle. See the discussion at
/// [writeValue].
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case _valueNull:
return null;
case _valueTrue:
return true;
case _valueFalse:
return false;
case _valueInt32:
return buffer.getInt32();
case _valueInt64:
return buffer.getInt64();
case _valueFloat64:
return buffer.getFloat64();
case _valueLargeInt:
case _valueString:
final int length = readSize(buffer);
return utf8.decoder.convert(buffer.getUint8List(length));
case _valueUint8List:
final int length = readSize(buffer);
return buffer.getUint8List(length);
case _valueInt32List:
final int length = readSize(buffer);
return buffer.getInt32List(length);
case _valueInt64List:
final int length = readSize(buffer);
return buffer.getInt64List(length);
case _valueFloat64List:
final int length = readSize(buffer);
return buffer.getFloat64List(length);
case _valueList:
final int length = readSize(buffer);
final List
Object? readValue(ReadBuffer buffer) {
if (!buffer.hasRemaining)
throw const FormatException('Message corrupted');
final int type = buffer.getUint8();
return readValueOfType(type, buffer);
}
/// Reads a value of the indicated [type] from [buffer].
///
/// The codec can be extended by overriding this method, calling super for
/// types that the extension does not handle. See the discussion at
/// [writeValue].
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case _valueNull:
return null;
case _valueTrue:
return true;
case _valueFalse:
return false;
case _valueInt32:
return buffer.getInt32();
case _valueInt64:
return buffer.getInt64();
case _valueFloat64:
return buffer.getFloat64();
case _valueLargeInt:
case _valueString:
final int length = readSize(buffer);
return utf8.decoder.convert(buffer.getUint8List(length));
case _valueUint8List:
final int length = readSize(buffer);
return buffer.getUint8List(length);
case _valueInt32List:
final int length = readSize(buffer);
return buffer.getInt32List(length);
case _valueInt64List:
final int length = readSize(buffer);
return buffer.getInt64List(length);
case _valueFloat64List:
final int length = readSize(buffer);
return buffer.getFloat64List(length);
case _valueList:
final int length = readSize(buffer);
final List result = List.filled(length, null, growable: false);
for (int i = 0; i < length; i++)
result[i] = readValue(buffer);
return result;
case _valueMap:
final int length = readSize(buffer);
final Map result = {};
for (int i = 0; i < length; i++)
result[readValue(buffer)] = readValue(buffer);
return result;
default: throw const FormatException('Message corrupted');
}
}
/** Reads a value as written by writeValue. */
protected final Object readValue(ByteBuffer buffer) {
if (!buffer.hasRemaining()) {
throw new IllegalArgumentException("Message corrupted");
}
final byte type = buffer.get();
return readValueOfType(type, buffer);
}
/**
* Reads a value of the specified type.
*
*
Subclasses may extend the codec by overriding this method, calling super for types that the
* extension does not handle.
*/
protected Object readValueOfType(byte type, ByteBuffer buffer) {
final Object result;
switch (type) {
case NULL:
result = null;
break;
case TRUE:
result = true;
break;
case FALSE:
result = false;
break;
case INT:
result = buffer.getInt();
break;
case LONG:
result = buffer.getLong();
break;
case BIGINT:
{
final byte[] hex = readBytes(buffer);
result = new BigInteger(new String(hex, UTF8), 16);
break;
}
case DOUBLE:
readAlignment(buffer, 8);
result = buffer.getDouble();
break;
case STRING:
{
final byte[] bytes = readBytes(buffer);
result = new String(bytes, UTF8);
break;
}
case BYTE_ARRAY:
{
result = readBytes(buffer);
break;
}
case INT_ARRAY:
{
final int length = readSize(buffer);
final int[] array = new int[length];
readAlignment(buffer, 4);
buffer.asIntBuffer().get(array);
result = array;
buffer.position(buffer.position() + 4 * length);
break;
}
case LONG_ARRAY:
{
final int length = readSize(buffer);
final long[] array = new long[length];
readAlignment(buffer, 8);
buffer.asLongBuffer().get(array);
result = array;
buffer.position(buffer.position() + 8 * length);
break;
}
case DOUBLE_ARRAY:
{
final int length = readSize(buffer);
final double[] array = new double[length];
readAlignment(buffer, 8);
buffer.asDoubleBuffer().get(array);
result = array;
buffer.position(buffer.position() + 8 * length);
break;
}
case LIST:
{
final int size = readSize(buffer);
final List list = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
list.add(readValue(buffer));
}
result = list;
break;
}
case MAP:
{
final int size = readSize(buffer);
final Map map = new HashMap<>();
for (int i = 0; i < size; i++) {
map.put(readValue(buffer), readValue(buffer));
}
result = map;
break;
}
default:
throw new IllegalArgumentException("Message corrupted");
}
return result;
}
/** Reads alignment padding bytes as written by writeAlignment. */
protected static final void readAlignment(ByteBuffer buffer, int alignment) {
final int mod = buffer.position() % alignment;
if (mod != 0) {
buffer.position(buffer.position() + alignment - mod);
}
}
Future _sendPlatformMessage(String channel, ByteData? message) {
final Completer completer = Completer();
// ui.PlatformDispatcher.instance is accessed directly instead of using
// ServicesBinding.instance.platformDispatcher because this method might be
// invoked before any binding is initialized. This issue was reported in
// #27541. It is not ideal to statically access
// ui.PlatformDispatcher.instance because the PlatformDispatcher may be
// dependency injected elsewhere with a different instance. However, static
// access at this location seems to be the least bad option.
ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (ByteData? reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message response callback'),
));
}
});
return completer.future;
}
// |PlatformView|
void PlatformMessageHandlerAndroid::HandlePlatformMessage(
std::unique_ptr message) {
// Called from the ui thread.
int response_id = next_response_id_++;
if (auto response = message->response()) {
std::lock_guard lock(pending_responses_mutex_);
pending_responses_[response_id] = response;
}
// This call can re-enter in InvokePlatformMessageXxxResponseCallback.
jni_facade_->FlutterViewHandlePlatformMessage(std::move(message),
response_id);
}
// Called by native.
// TODO(mattcarroll): determine if message is nonull or nullable
@SuppressWarnings("unused")
@VisibleForTesting
public void handlePlatformMessage(
@NonNull final String channel, byte[] message, final int replyId) {
if (platformMessageHandler != null) {
platformMessageHandler.handleMessageFromDart(channel, message, replyId);
}
// TODO(mattcarroll): log dropped messages when in debug mode
// (https://github.com/flutter/flutter/issues/25391)
}
这里的platformMessageHandler其实就是初始化赋值的DartMessenger
DartMessenger.handleMessageFromDart
@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);
} catch (Error err) {
handleError(err);
}
} else {
Log.v(TAG, "No registered handler for message. Responding to Dart with empty reply message.");
flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
}
public void invokePlatformMessageResponseCallback(
int responseId, @Nullable ByteBuffer message, int position) {
ensureRunningOnMainThread();
if (isAttached()) {
nativeInvokePlatformMessageResponseCallback(
nativeShellHolderId, 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);
}
}
void PlatformMessageHandlerAndroid::InvokePlatformMessageResponseCallback(
int response_id,
std::unique_ptr mapping) {
// Called from any thread.
if (!response_id) {
return;
}
// TODO(gaaclarke): Move the jump to the ui thread here from
// PlatformMessageResponseDart so we won't need to use a mutex anymore.
fml::RefPtr message_response;
{
std::lock_guard lock(pending_responses_mutex_);
auto it = pending_responses_.find(response_id);
if (it == pending_responses_.end())
return;
message_response = std::move(it->second);
pending_responses_.erase(it);
}
message_response->Complete(std::move(mapping));
}
Future _sendPlatformMessage(String channel, ByteData? message) {
final Completer completer = Completer();
// ui.PlatformDispatcher.instance is accessed directly instead of using
// ServicesBinding.instance.platformDispatcher because this method might be
// invoked before any binding is initialized. This issue was reported in
// #27541. It is not ideal to statically access
// ui.PlatformDispatcher.instance because the PlatformDispatcher may be
// dependency injected elsewhere with a different instance. However, static
// access at this location seems to be the least bad option.
ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (ByteData? reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message response callback'),
));
}
});
return completer.future;
}
/** 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(nativeShellHolderId, 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);
}
}
void PlatformConfiguration::CompletePlatformMessageResponse(
int response_id,
std::vector data) {
if (!response_id) {
return;
}
auto it = pending_responses_.find(response_id);
if (it == pending_responses_.end()) {
return;
}
auto response = std::move(it->second);
pending_responses_.erase(it);
response->Complete(std::make_unique(std::move(data)));
}
void PlatformViewAndroidJNIImpl::FlutterViewHandlePlatformMessageResponse(
int responseId,
std::unique_ptr data) {
// We are on the platform thread. Attempt to get the strong reference to
// the Java object.
JNIEnv* env = fml::jni::AttachCurrentThread();
auto java_object = java_object_.get(env);
if (java_object.is_null()) {
// The Java object was collected before this message response got to
// it. Drop the response on the floor.
return;
}
if (data == nullptr) { // Empty response.
env->CallVoidMethod(java_object.obj(),
g_handle_platform_message_response_method, responseId,
nullptr);
} else {
// Convert the vector to a Java byte array.
fml::jni::ScopedJavaLocalRef data_array(
env, env->NewDirectByteBuffer(const_cast(data->GetMapping()),
data->GetSize()));
env->CallVoidMethod(java_object.obj(),
g_handle_platform_message_response_method, responseId,
data_array.obj());
}
FML_CHECK(fml::jni::CheckException(env));
}
g_handle_platform_message_response_method = env->GetMethodID(
g_flutter_jni_class->obj(), "handlePlatformMessageResponse",
"(ILjava/nio/ByteBuffer;)V");
g_flutter_jni_class = new fml::jni::ScopedJavaGlobalRef(
env, env->FindClass("io/flutter/embedding/engine/FlutterJNI"));
// Called by native to respond to a platform message that we sent.
// TODO(mattcarroll): determine if reply is nonull or nullable
@SuppressWarnings("unused")
private void handlePlatformMessageResponse(int replyId, byte[] reply) {
if (platformMessageHandler != null) {
platformMessageHandler.handlePlatformMessageResponse(replyId, reply);
}
// TODO(mattcarroll): log dropped messages when in debug mode
// (https://github.com/flutter/flutter/issues/25391)
}