| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package io.flutter.plugin.common; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.UiThread; |
| import io.flutter.BuildConfig; |
| import io.flutter.Log; |
| import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler; |
| import io.flutter.plugin.common.BinaryMessenger.BinaryReply; |
| import java.nio.ByteBuffer; |
| import java.nio.charset.Charset; |
| import java.util.Locale; |
| |
| /** |
| * A named channel for communicating with the Flutter application using basic, asynchronous message |
| * passing. |
| * |
| * <p>Messages are encoded into binary before being sent, and binary messages received are decoded |
| * into Java objects. The {@link MessageCodec} used must be compatible with the one used by the |
| * Flutter application. This can be achieved by creating a <a |
| * href="https://api.flutter.dev/flutter/services/BasicMessageChannel-class.html">BasicMessageChannel</a> |
| * counterpart of this channel on the Dart side. The static Java type of messages sent and received |
| * is {@code Object}, but only values supported by the specified {@link MessageCodec} can be used. |
| * |
| * <p>The logical identity of the channel is given by its name. Identically named channels will |
| * interfere with each other's communication. |
| */ |
| public final class BasicMessageChannel<T> { |
| private static final String TAG = "BasicMessageChannel#"; |
| public static final String CHANNEL_BUFFERS_CHANNEL = "dev.flutter/channel-buffers"; |
| |
| @NonNull private final BinaryMessenger messenger; |
| @NonNull private final String name; |
| @NonNull private final MessageCodec<T> codec; |
| @Nullable private final BinaryMessenger.TaskQueue taskQueue; |
| |
| /** |
| * Creates a new channel associated with the specified {@link BinaryMessenger} and with the |
| * specified name and {@link MessageCodec}. |
| * |
| * @param messenger a {@link BinaryMessenger}. |
| * @param name a channel name String. |
| * @param codec a {@link MessageCodec}. |
| */ |
| public BasicMessageChannel( |
| @NonNull BinaryMessenger messenger, @NonNull String name, @NonNull MessageCodec<T> codec) { |
| this(messenger, name, codec, null); |
| } |
| |
| /** |
| * Creates a new channel associated with the specified {@link BinaryMessenger} and with the |
| * specified name and {@link MessageCodec}. |
| * |
| * @param messenger a {@link BinaryMessenger}. |
| * @param name a channel name String. |
| * @param codec a {@link MessageCodec}. |
| * @param taskQueue a {@link BinaryMessenger.TaskQueue} that specifies what thread will execute |
| * the handler. Specifying null means execute on the platform thread. See also {@link |
| * BinaryMessenger#makeBackgroundTaskQueue()}. |
| */ |
| public BasicMessageChannel( |
| @NonNull BinaryMessenger messenger, |
| @NonNull String name, |
| @NonNull MessageCodec<T> codec, |
| BinaryMessenger.TaskQueue taskQueue) { |
| if (BuildConfig.DEBUG) { |
| if (messenger == null) { |
| Log.e(TAG, "Parameter messenger must not be null."); |
| } |
| if (name == null) { |
| Log.e(TAG, "Parameter name must not be null."); |
| } |
| if (codec == null) { |
| Log.e(TAG, "Parameter codec must not be null."); |
| } |
| } |
| this.messenger = messenger; |
| this.name = name; |
| this.codec = codec; |
| this.taskQueue = taskQueue; |
| } |
| |
| /** |
| * Sends the specified message to the Flutter application on this channel. |
| * |
| * @param message the message, possibly null. |
| */ |
| public void send(@Nullable T message) { |
| send(message, null); |
| } |
| |
| /** |
| * Sends the specified message to the Flutter application, optionally expecting a reply. |
| * |
| * <p>Any uncaught exception thrown by the reply callback will be caught and logged. |
| * |
| * @param message the message, possibly null. |
| * @param callback a {@link Reply} callback, possibly null. |
| */ |
| @UiThread |
| public void send(@Nullable T message, @Nullable final Reply<T> callback) { |
| messenger.send( |
| name, |
| codec.encodeMessage(message), |
| callback == null ? null : new IncomingReplyHandler(callback)); |
| } |
| |
| /** |
| * Registers a message handler on this channel for receiving messages sent from the Flutter |
| * application. |
| * |
| * <p>Overrides any existing handler registration for (the name of) this channel. |
| * |
| * <p>If no handler has been registered, any incoming message on this channel will be handled |
| * silently by sending a null reply. |
| * |
| * @param handler a {@link MessageHandler}, or null to deregister. |
| */ |
| @UiThread |
| public void setMessageHandler(@Nullable final MessageHandler<T> handler) { |
| // We call the 2 parameter variant specifically to avoid breaking changes in |
| // mock verify calls. |
| // See https://github.com/flutter/flutter/issues/92582. |
| if (taskQueue != null) { |
| messenger.setMessageHandler( |
| name, handler == null ? null : new IncomingMessageHandler(handler), taskQueue); |
| } else { |
| messenger.setMessageHandler( |
| name, handler == null ? null : new IncomingMessageHandler(handler)); |
| } |
| } |
| |
| /** |
| * Adjusts the number of messages that will get buffered when sending messages to channels that |
| * aren't fully set up yet. For example, the engine isn't running yet or the channel's message |
| * handler isn't set up on the Dart side yet. |
| */ |
| public void resizeChannelBuffer(int newSize) { |
| resizeChannelBuffer(messenger, name, newSize); |
| } |
| |
| static void resizeChannelBuffer( |
| @NonNull BinaryMessenger messenger, @NonNull String channel, int newSize) { |
| Charset charset = Charset.forName("UTF-8"); |
| String messageString = String.format(Locale.US, "resize\r%s\r%d", channel, newSize); |
| ByteBuffer message = ByteBuffer.wrap(messageString.getBytes(charset)); |
| messenger.send(CHANNEL_BUFFERS_CHANNEL, message); |
| } |
| |
| /** A handler of incoming messages. */ |
| public interface MessageHandler<T> { |
| |
| /** |
| * Handles the specified message received from Flutter. |
| * |
| * <p>Handler implementations must reply to all incoming messages, by submitting a single reply |
| * message to the given {@link Reply}. Failure to do so will result in lingering Flutter reply |
| * handlers. The reply may be submitted asynchronously and invoked on any thread. |
| * |
| * <p>Any uncaught exception thrown by this method, or the preceding message decoding, will be |
| * caught by the channel implementation and logged, and a null reply message will be sent back |
| * to Flutter. |
| * |
| * <p>Any uncaught exception thrown during encoding a reply message submitted to the {@link |
| * Reply} is treated similarly: the exception is logged, and a null reply is sent to Flutter. |
| * |
| * @param message the message, possibly null. |
| * @param reply a {@link Reply} for sending a single message reply back to Flutter. |
| */ |
| void onMessage(@Nullable T message, @NonNull Reply<T> reply); |
| } |
| |
| /** |
| * Message reply callback. Used to submit a reply to an incoming message from Flutter. Also used |
| * in the dual capacity to handle a reply received from Flutter after sending a message. |
| */ |
| public interface Reply<T> { |
| /** |
| * Handles the specified message reply. |
| * |
| * @param reply the reply, possibly null. |
| */ |
| void reply(@Nullable T reply); |
| } |
| |
| private final class IncomingReplyHandler implements BinaryReply { |
| private final Reply<T> callback; |
| |
| private IncomingReplyHandler(@NonNull Reply<T> callback) { |
| this.callback = callback; |
| } |
| |
| @Override |
| public void reply(@Nullable ByteBuffer reply) { |
| try { |
| callback.reply(codec.decodeMessage(reply)); |
| } catch (RuntimeException e) { |
| Log.e(TAG + name, "Failed to handle message reply", e); |
| } |
| } |
| } |
| |
| private final class IncomingMessageHandler implements BinaryMessageHandler { |
| private final MessageHandler<T> handler; |
| |
| private IncomingMessageHandler(@NonNull MessageHandler<T> handler) { |
| this.handler = handler; |
| } |
| |
| @Override |
| public void onMessage(@Nullable ByteBuffer message, @NonNull final BinaryReply callback) { |
| try { |
| handler.onMessage( |
| codec.decodeMessage(message), |
| new Reply<T>() { |
| @Override |
| public void reply(T reply) { |
| callback.reply(codec.encodeMessage(reply)); |
| } |
| }); |
| } catch (RuntimeException e) { |
| Log.e(TAG + name, "Failed to handle message", e); |
| callback.reply(null); |
| } |
| } |
| } |
| } |