mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Clarify semantics of channel-related errors (#3607)
Mainly improvements to javadoc and error logging. A bit of refactoring.
This commit is contained in:
parent
3ed4684a09
commit
4eed7d2732
@ -6,24 +6,25 @@ package io.flutter.plugin.common;
|
||||
|
||||
import android.util.Log;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import io.flutter.plugin.common.BinaryMessenger.BinaryReply;
|
||||
import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler;
|
||||
|
||||
/**
|
||||
* A named channel for communicating with the Flutter application using semi-structured messages.
|
||||
* A named channel for communicating with the Flutter application using basic, asynchronous message passing.
|
||||
*
|
||||
* Messages are encoded into binary before being sent, and binary messages received are decoded
|
||||
* <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 PlatformChannel
|
||||
* one used by the Flutter application. This can be achieved by creating a
|
||||
* <a href="https://docs.flutter.io/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 Object, but only values supported by the specified {@link MessageCodec} can be used.
|
||||
* is {@code Object}, but only values supported by the specified {@link MessageCodec} can be used.</p>
|
||||
*
|
||||
* The channel supports basic message send/receive operations. All communication is asynchronous.
|
||||
*
|
||||
* Identically named channels may interfere with each other's communication.
|
||||
* <p>The logical identity of the channel is given by its name. Identically named channels will interfere
|
||||
* with each other's communication.</p>
|
||||
*/
|
||||
public final class BasicMessageChannel<T> {
|
||||
private static final String TAG = "FlutterMessageChannel#";
|
||||
private static final String TAG = "BasicMessageChannel#";
|
||||
|
||||
private final BinaryMessenger messenger;
|
||||
private final String name;
|
||||
@ -58,6 +59,8 @@ public final class BasicMessageChannel<T> {
|
||||
/**
|
||||
* 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.</p>
|
||||
*
|
||||
* @param message the message, possibly null.
|
||||
* @param callback a {@link Reply} callback, possibly null.
|
||||
*/
|
||||
@ -67,9 +70,13 @@ public final class BasicMessageChannel<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a message handler on this channel.
|
||||
* Registers a message handler on this channel for receiving messages sent from the Flutter
|
||||
* application.
|
||||
*
|
||||
* Overrides any existing handler registration (for messages, method calls, or streams).
|
||||
* <p>Overrides any existing handler registration for (the name of) this channel.</p>
|
||||
*
|
||||
* <p>If no handler has been registered, any incoming message on this channel will be handled silently
|
||||
* by sending a null reply.</p>
|
||||
*
|
||||
* @param handler a {@link MessageHandler}, or null to deregister.
|
||||
*/
|
||||
@ -84,10 +91,21 @@ public final class BasicMessageChannel<T> {
|
||||
public interface MessageHandler<T> {
|
||||
|
||||
/**
|
||||
* Handles the specified message.
|
||||
* 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.</p>
|
||||
*
|
||||
* <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>
|
||||
*
|
||||
* <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.</p>
|
||||
*
|
||||
* @param message the message, possibly null.
|
||||
* @param reply a {@link Reply} for providing a single message reply.
|
||||
* @param reply a {@link Reply} for sending a single message reply back to Flutter.
|
||||
*/
|
||||
void onMessage(T message, Reply<T> reply);
|
||||
}
|
||||
@ -117,7 +135,7 @@ public final class BasicMessageChannel<T> {
|
||||
public void reply(ByteBuffer reply) {
|
||||
try {
|
||||
callback.reply(codec.decodeMessage(reply));
|
||||
} catch (Exception e) {
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG + name, "Failed to handle message reply", e);
|
||||
}
|
||||
}
|
||||
@ -134,18 +152,17 @@ public final class BasicMessageChannel<T> {
|
||||
public void onMessage(ByteBuffer message, final BinaryReply callback) {
|
||||
try {
|
||||
handler.onMessage(codec.decodeMessage(message), new Reply<T>() {
|
||||
private boolean done = false;
|
||||
|
||||
@Override
|
||||
public void reply(T reply) {
|
||||
if (done) {
|
||||
throw new IllegalStateException("Call result already provided");
|
||||
try {
|
||||
callback.reply(codec.encodeMessage(reply));
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG + name, "Failed to encode reply", e);
|
||||
callback.reply(null);
|
||||
}
|
||||
callback.reply(codec.encodeMessage(reply));
|
||||
done = true;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG + name, "Failed to handle message", e);
|
||||
callback.reply(null);
|
||||
}
|
||||
|
||||
@ -4,7 +4,9 @@ import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Facility for communicating with Flutter using asynchronous message passing with binary messages.
|
||||
* The Flutter Dart code can use {@code BinaryMessages} to participate.
|
||||
* The Flutter Dart code should use
|
||||
* <a href="https://docs.flutter.io/flutter/services/BinaryMessages-class.html">BinaryMessages</a>
|
||||
* to participate.
|
||||
*
|
||||
* @see BasicMessageChannel , which supports message passing with Strings and semi-structured messages.
|
||||
* @see MethodChannel , which supports communication using asynchronous method invocation.
|
||||
@ -12,19 +14,20 @@ import java.nio.ByteBuffer;
|
||||
*/
|
||||
public interface BinaryMessenger {
|
||||
/**
|
||||
* Sends a binary message to the Flutter application. The Flutter Dart code can register a
|
||||
* platform message handler with {@code BinaryMessages} that will receive these messages.
|
||||
* Sends a binary message to the Flutter application.
|
||||
*
|
||||
* @param channel the name {@link String} of the channel that will receive this message.
|
||||
* @param channel the name {@link String} of the logical channel used for the message.
|
||||
* @param message the message payload, a {@link ByteBuffer} with the message bytes between position
|
||||
* zero and current position, or null.
|
||||
*/
|
||||
void send(String channel, ByteBuffer message);
|
||||
|
||||
/**
|
||||
* Sends a binary message to the Flutter application,
|
||||
* Sends a binary message to the Flutter application, optionally expecting a reply.
|
||||
*
|
||||
* @param channel the name {@link String} of the channel that will receive this message.
|
||||
* <p>Any uncaught exception thrown by the reply callback will be caught and logged.</p>
|
||||
*
|
||||
* @param channel the name {@link String} of the logical channel used for the message.
|
||||
* @param message the message payload, a {@link ByteBuffer} with the message bytes between position
|
||||
* zero and current position, or null.
|
||||
* @param callback a {@link BinaryReply} callback invoked when the Flutter application responds to the
|
||||
@ -36,11 +39,14 @@ public interface BinaryMessenger {
|
||||
* Registers a handler to be invoked when the Flutter application sends a message
|
||||
* to its host platform.
|
||||
*
|
||||
* Registration overwrites any previous registration for the same channel name.
|
||||
* Use a {@code null} handler to unregister.
|
||||
* <p>Registration overwrites any previous registration for the same channel name.
|
||||
* Use a null handler to deregister.</p>
|
||||
*
|
||||
* <p>If no handler has been registered for a particular channel, any incoming message on
|
||||
* that channel will be handled silently by sending a null reply.</p>
|
||||
*
|
||||
* @param channel the name {@link String} of the channel.
|
||||
* @param handler a {@link BinaryMessageHandler} to be invoked on incoming messages.
|
||||
* @param handler a {@link BinaryMessageHandler} to be invoked on incoming messages, or null.
|
||||
*/
|
||||
void setMessageHandler(String channel, BinaryMessageHandler handler);
|
||||
|
||||
@ -51,7 +57,14 @@ public interface BinaryMessenger {
|
||||
/**
|
||||
* Handles the specified message.
|
||||
*
|
||||
* @param message the message {@link ByteBuffer} payload.
|
||||
* <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>
|
||||
*
|
||||
* <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.</p>
|
||||
*
|
||||
* @param message the message {@link ByteBuffer} payload, possibly null.
|
||||
* @param reply A {@link BinaryReply} used for submitting a reply back to Flutter.
|
||||
*/
|
||||
void onMessage(ByteBuffer message, BinaryReply reply);
|
||||
|
||||
@ -9,22 +9,24 @@ import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler;
|
||||
import io.flutter.plugin.common.BinaryMessenger.BinaryReply;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* A named channel for communicating with the Flutter application using asynchronous
|
||||
* event streams.
|
||||
*
|
||||
* Incoming requests for event stream setup are decoded from binary on receipt, and
|
||||
* <p>Incoming requests for event stream setup are decoded from binary on receipt, and
|
||||
* Java responses and events are encoded into binary before being transmitted back
|
||||
* to Flutter. The {@link MethodCodec} used must be compatible with the one used by
|
||||
* the Flutter application. This can be achieved by creating a PlatformEventChannel
|
||||
* counterpart of this channel on the Flutter side. The Java type of responses and
|
||||
* events is Object, but only values supported by the specified {@link MethodCodec}
|
||||
* can be used.
|
||||
* the Flutter application. This can be achieved by creating an
|
||||
* <a href="https://docs.flutter.io/flutter/services/EventChannel-class.html">EventChannel</a>
|
||||
* counterpart of this channel on the Dart side. The Java type of stream configuration arguments,
|
||||
* events, and error details is {@code Object}, but only values supported by the specified
|
||||
* {@link MethodCodec} can be used.</p>
|
||||
*
|
||||
* The identity of the channel is given by its name, so other uses of that name
|
||||
* with may interfere with this channel's communication.
|
||||
* <p>The logical identity of the channel is given by its name. Identically named channels will interfere
|
||||
* with each other's communication.</p>
|
||||
*/
|
||||
public final class EventChannel {
|
||||
private static final String TAG = "EventChannel#";
|
||||
@ -64,7 +66,10 @@ public final class EventChannel {
|
||||
/**
|
||||
* Registers a stream handler on this channel.
|
||||
*
|
||||
* Overrides any existing handler registration.
|
||||
* <p>Overrides any existing handler registration for (the name of) this channel.</p>
|
||||
*
|
||||
* <p>If no handler has been registered, any incoming stream setup requests will be handled
|
||||
* silently by providing an empty stream.</p>
|
||||
*
|
||||
* @param handler a {@link StreamHandler}, or null to deregister.
|
||||
*/
|
||||
@ -72,56 +77,75 @@ public final class EventChannel {
|
||||
messenger.setMessageHandler(name, handler == null ? null : new IncomingStreamRequestHandler(handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Event callback. Supports dual use: Producers of events to be sent to Flutter
|
||||
* act as clients of this interface for sending events. Consumers of events sent
|
||||
* from Flutter implement this interface for handling received events.
|
||||
*/
|
||||
public interface EventSink {
|
||||
/**
|
||||
* Consumes a successful event.
|
||||
*
|
||||
* @param event The event, possibly null.
|
||||
*/
|
||||
void success(Object event);
|
||||
|
||||
/**
|
||||
* Consumes an error event.
|
||||
*
|
||||
* @param errorCode An error code String.
|
||||
* @param errorMessage A human-readable error message String, possibly null.
|
||||
* @param errorDetails Error details, possibly null
|
||||
*/
|
||||
void error(String errorCode, String errorMessage, Object errorDetails);
|
||||
|
||||
/**
|
||||
* Consumes end of stream. No calls to {@link #success(Object)} or
|
||||
* {@link #error(String, String, Object)} will be made following a call
|
||||
* to this method.
|
||||
*/
|
||||
void endOfStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler of stream setup and tear-down requests.
|
||||
*
|
||||
* <p>Implementations must be prepared to accept sequences of alternating calls to
|
||||
* {@link #onListen(Object, EventSink)} and {@link #onCancel(Object)}. Implementations
|
||||
* should ideally consume no resources when the last such call is not {@code onListen}.
|
||||
* In typical situations, this means that the implementation should register itself
|
||||
* with platform-specific event sources {@code onListen} and deregister again
|
||||
* {@code onCancel}.</p>
|
||||
*/
|
||||
public interface StreamHandler {
|
||||
/**
|
||||
* Handles a request to set up an event stream.
|
||||
*
|
||||
* @param arguments Stream configuration arguments, possibly null.
|
||||
* @param events An {@link EventSink} for emitting events to the Flutter receiver.
|
||||
* <p>Any uncaught exception thrown by this method, or the preceding arguments
|
||||
* decoding, will be caught by the channel implementation and logged. An error result
|
||||
* message will be sent back to Flutter.</p>
|
||||
*
|
||||
* <p>Any uncaught exception thrown during encoding an event or error submitted to the
|
||||
* {@link EventSink} is treated similarly: the exception is logged, and an error event
|
||||
* is sent to Flutter.</p>
|
||||
*
|
||||
* @param arguments stream configuration arguments, possibly null.
|
||||
* @param events an {@link EventSink} for emitting events to the Flutter receiver.
|
||||
*/
|
||||
void onListen(Object arguments, EventSink events);
|
||||
|
||||
/**
|
||||
* Handles a request to tear down an event stream.
|
||||
*
|
||||
* @param arguments Stream configuration arguments, possibly null.
|
||||
* <p>Any uncaught exception thrown by this method, or the preceding arguments
|
||||
* decoding, will be caught by the channel implementation and logged. An error result
|
||||
* result message will be sent back to Flutter.</p>
|
||||
*
|
||||
* @param arguments stream configuration arguments, possibly null.
|
||||
*/
|
||||
void onCancel(Object arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event callback. Supports dual use: Producers of events to be sent to Flutter
|
||||
* act as clients of this interface for sending events. Consumers of events sent
|
||||
* from Flutter implement this interface for handling received events (the latter
|
||||
* facility has not been implemented yet).
|
||||
*/
|
||||
public interface EventSink {
|
||||
/**
|
||||
* Consumes a successful event.
|
||||
*
|
||||
* @param event the event, possibly null.
|
||||
*/
|
||||
void success(Object event);
|
||||
|
||||
/**
|
||||
* Consumes an error event.
|
||||
*
|
||||
* @param errorCode an error code String.
|
||||
* @param errorMessage a human-readable error message String, possibly null.
|
||||
* @param errorDetails error details, possibly null
|
||||
*/
|
||||
void error(String errorCode, String errorMessage, Object errorDetails);
|
||||
|
||||
/**
|
||||
* Consumes end of stream. Ensuing calls to {@link #success(Object)} or
|
||||
* {@link #error(String, String, Object)}, if any, are ignored.
|
||||
*/
|
||||
void endOfStream();
|
||||
}
|
||||
|
||||
private final class IncomingStreamRequestHandler implements BinaryMessageHandler {
|
||||
private final StreamHandler handler;
|
||||
private final AtomicReference<EventSink> activeSink = new AtomicReference<>(null);
|
||||
@ -132,13 +156,18 @@ public final class EventChannel {
|
||||
|
||||
@Override
|
||||
public void onMessage(ByteBuffer message, final BinaryReply reply) {
|
||||
final MethodCall call = codec.decodeMethodCall(message);
|
||||
if (call.method.equals("listen")) {
|
||||
onListen(call.arguments, reply);
|
||||
} else if (call.method.equals("cancel")) {
|
||||
onCancel(call.arguments, reply);
|
||||
} else {
|
||||
reply.reply(null);
|
||||
try {
|
||||
final MethodCall call = codec.decodeMethodCall(message);
|
||||
if (call.method.equals("listen")) {
|
||||
onListen(call.arguments, reply);
|
||||
} else if (call.method.equals("cancel")) {
|
||||
onCancel(call.arguments, reply);
|
||||
} else {
|
||||
reply.reply(null);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG + name, "Failed to decode event stream lifecycle call", e);
|
||||
reply.reply(codec.encodeErrorEnvelope("decode", e.getMessage(), null));
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,10 +177,10 @@ public final class EventChannel {
|
||||
try {
|
||||
handler.onListen(arguments, eventSink);
|
||||
callback.reply(codec.encodeSuccessEnvelope(null));
|
||||
} catch (Exception e) {
|
||||
} catch (RuntimeException e) {
|
||||
activeSink.set(null);
|
||||
Log.e(TAG + name, "Failed to open event stream", e);
|
||||
callback.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
|
||||
callback.reply(codec.encodeErrorEnvelope("uncaught", e.getMessage(), null));
|
||||
}
|
||||
} else {
|
||||
callback.reply(codec.encodeErrorEnvelope("error", "Stream already active", null));
|
||||
@ -164,9 +193,9 @@ public final class EventChannel {
|
||||
try {
|
||||
handler.onCancel(arguments);
|
||||
callback.reply(codec.encodeSuccessEnvelope(null));
|
||||
} catch (Exception e) {
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG + name, "Failed to close event stream", e);
|
||||
callback.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
|
||||
callback.reply(codec.encodeErrorEnvelope("uncaught", e.getMessage(), null));
|
||||
}
|
||||
} else {
|
||||
callback.reply(codec.encodeErrorEnvelope("error", "No active stream to cancel", null));
|
||||
@ -174,30 +203,39 @@ public final class EventChannel {
|
||||
}
|
||||
|
||||
private final class EventSinkImplementation implements EventSink {
|
||||
final AtomicBoolean hasEnded = new AtomicBoolean(false);
|
||||
|
||||
@Override
|
||||
public void success(Object event) {
|
||||
if (activeSink.get() != this) {
|
||||
if (hasEnded.get() || activeSink.get() != this) {
|
||||
return;
|
||||
}
|
||||
EventChannel.this.messenger.send(
|
||||
name,
|
||||
codec.encodeSuccessEnvelope(event));
|
||||
try {
|
||||
EventChannel.this.messenger.send(name, codec.encodeSuccessEnvelope(event));
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG + name, "Failed to encode event", e);
|
||||
EventChannel.this.messenger.send(name, codec.encodeErrorEnvelope("encode", e.getMessage(), null));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String errorCode, String errorMessage,
|
||||
Object errorDetails) {
|
||||
if (activeSink.get() != this) {
|
||||
public void error(String errorCode, String errorMessage, Object errorDetails) {
|
||||
if (hasEnded.get() || activeSink.get() != this) {
|
||||
return;
|
||||
}
|
||||
EventChannel.this.messenger.send(
|
||||
name,
|
||||
codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
|
||||
try {
|
||||
EventChannel.this.messenger.send(
|
||||
name,
|
||||
codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG + name, "Failed to encode error", e);
|
||||
EventChannel.this.messenger.send(name, codec.encodeErrorEnvelope("encode", e.getMessage(), null));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endOfStream() {
|
||||
if (activeSink.get() != this) {
|
||||
if (hasEnded.getAndSet(true) || activeSink.get() != this) {
|
||||
return;
|
||||
}
|
||||
EventChannel.this.messenger.send(name, null);
|
||||
|
||||
@ -11,6 +11,7 @@ import org.json.JSONObject;
|
||||
* {@link JSONMessageCodec}.
|
||||
*/
|
||||
public final class JSONMethodCodec implements MethodCodec {
|
||||
// This codec must match the Dart codec of the same name in package flutter/services.
|
||||
public static final JSONMethodCodec INSTANCE = new JSONMethodCodec();
|
||||
|
||||
private JSONMethodCodec() {
|
||||
|
||||
@ -13,15 +13,16 @@ import java.nio.ByteBuffer;
|
||||
* A named channel for communicating with the Flutter application using asynchronous
|
||||
* method calls.
|
||||
*
|
||||
* Incoming method calls are decoded from binary on receipt, and Java results are encoded
|
||||
* <p>Incoming method calls are decoded from binary on receipt, and Java results are encoded
|
||||
* into binary before being transmitted back to Flutter. The {@link MethodCodec} used must be
|
||||
* compatible with the one used by the Flutter application. This can be achieved
|
||||
* by creating a PlatformMethodChannel counterpart of this channel on the
|
||||
* Flutter side. The Java type of method call arguments and results is Object,
|
||||
* but only values supported by the specified {@link MethodCodec} can be used.
|
||||
* by creating a
|
||||
* <a href="https://docs.flutter.io/flutter/services/MethodChannel-class.html">MethodChannel</a>
|
||||
* counterpart of this channel on the Dart side. The Java type of method call arguments and results is
|
||||
* {@code Object}, but only values supported by the specified {@link MethodCodec} can be used.</p>
|
||||
*
|
||||
* The identity of the channel is given by its name, so other uses of that name
|
||||
* with may interfere with this channel's communication.
|
||||
* <p>The logical identity of the channel is given by its name. Identically named channels will interfere
|
||||
* with each other's communication.</p>
|
||||
*/
|
||||
public final class MethodChannel {
|
||||
private static final String TAG = "MethodChannel#";
|
||||
@ -69,11 +70,13 @@ public final class MethodChannel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a method on this channel.
|
||||
* Invokes a method on this channel, optionally expecting a result.
|
||||
*
|
||||
* <p>Any uncaught exception thrown by the result callback will be caught and logged.</p>
|
||||
*
|
||||
* @param method the name String of the method.
|
||||
* @param arguments the arguments for the invocation, possibly null.
|
||||
* @param callback a {@link Result} callback for the invocation result.
|
||||
* @param callback a {@link Result} callback for the invocation result, or null.
|
||||
*/
|
||||
public void invokeMethod(String method, Object arguments, Result callback) {
|
||||
messenger.send(name, codec.encodeMethodCall(new MethodCall(method, arguments)),
|
||||
@ -83,7 +86,14 @@ public final class MethodChannel {
|
||||
/**
|
||||
* Registers a method call handler on this channel.
|
||||
*
|
||||
* Overrides any existing handler registration.
|
||||
* <p>Overrides any existing handler registration for (the name of) this channel.</p>
|
||||
*
|
||||
* <p>If no handler has been registered, any incoming method call on this channel will be handled
|
||||
* silently by sending a null reply. This results in a
|
||||
* <a href="https://docs.flutter.io/flutter/services/MissingPluginException-class.html">MissingPluginException</a>
|
||||
* on the Dart side, unless an
|
||||
* <a href="https://docs.flutter.io/flutter/services/OptionalMethodChannel-class.html">OptionalMethodChannel</a>
|
||||
* is used.</p>
|
||||
*
|
||||
* @param handler a {@link MethodCallHandler}, or null to deregister.
|
||||
*/
|
||||
@ -92,6 +102,30 @@ public final class MethodChannel {
|
||||
handler == null ? null : new IncomingMethodCallHandler(handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler of incoming method calls.
|
||||
*/
|
||||
public interface MethodCallHandler {
|
||||
/**
|
||||
* Handles the specified method call received from Flutter.
|
||||
*
|
||||
* <p>Handler implementations must submit a result for all incoming calls, by making a single call
|
||||
* on the given {@link Result} callback. Failure to do so will result in lingering Flutter result
|
||||
* handlers. The result may be submitted asynchronously. Calls to unknown or unimplemented methods
|
||||
* should be handled using {@link Result#notImplemented()}.</p>
|
||||
*
|
||||
* <p>Any uncaught exception thrown by this method, or the preceding method call decoding, will be
|
||||
* caught by the channel implementation and logged, and an error result will be sent back to Flutter.</p>
|
||||
*
|
||||
* <p>Any uncaught exception thrown during encoding a result submitted to the {@link Result}
|
||||
* is treated similarly: the exception is logged, and an error result is sent to Flutter.</p>
|
||||
*
|
||||
* @param call A {@link MethodCall}.
|
||||
* @param result A {@link Result} used for submitting the result of the call.
|
||||
*/
|
||||
void onMethodCall(MethodCall call, Result result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method call result callback. Supports dual use: Implementations of methods
|
||||
* to be invoked by Flutter act as clients of this interface for sending results
|
||||
@ -121,19 +155,6 @@ public final class MethodChannel {
|
||||
void notImplemented();
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler of incoming method calls.
|
||||
*/
|
||||
public interface MethodCallHandler {
|
||||
/**
|
||||
* Handles the specified method call.
|
||||
*
|
||||
* @param call A {@link MethodCall}.
|
||||
* @param result A {@link Result} used for submitting the result of the call.
|
||||
*/
|
||||
void onMethodCall(MethodCall call, Result result);
|
||||
}
|
||||
|
||||
private final class IncomingResultHandler implements BinaryReply {
|
||||
private final Result callback;
|
||||
|
||||
@ -143,15 +164,19 @@ public final class MethodChannel {
|
||||
|
||||
@Override
|
||||
public void reply(ByteBuffer reply) {
|
||||
if (reply == null) {
|
||||
callback.notImplemented();
|
||||
} else {
|
||||
try {
|
||||
final Object result = codec.decodeEnvelope(reply);
|
||||
callback.success(result);
|
||||
} catch (FlutterException e) {
|
||||
callback.error(e.code, e.getMessage(), e.details);
|
||||
try {
|
||||
if (reply == null) {
|
||||
callback.notImplemented();
|
||||
} else {
|
||||
try {
|
||||
final Object result = codec.decodeEnvelope(reply);
|
||||
callback.success(result);
|
||||
} catch (FlutterException e) {
|
||||
callback.error(e.code, e.getMessage(), e.details);
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG + name, "Failed to handle method call result", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -165,42 +190,45 @@ public final class MethodChannel {
|
||||
|
||||
@Override
|
||||
public void onMessage(ByteBuffer message, final BinaryReply reply) {
|
||||
final MethodCall call = codec.decodeMethodCall(message);
|
||||
MethodCall call;
|
||||
try {
|
||||
call = codec.decodeMethodCall(message);
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG + name, "Failed to decode method call", e);
|
||||
reply.reply(codec.encodeErrorEnvelope("decode", e.getMessage(), null));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
handler.onMethodCall(call, new Result() {
|
||||
private boolean done = false;
|
||||
|
||||
@Override
|
||||
public void success(Object result) {
|
||||
checkDone();
|
||||
reply.reply(codec.encodeSuccessEnvelope(result));
|
||||
done = true;
|
||||
try {
|
||||
reply.reply(codec.encodeSuccessEnvelope(result));
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG + name, "Failed to encode success result", e);
|
||||
reply.reply(codec.encodeErrorEnvelope("encode", e.getMessage(), null));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String errorCode, String errorMessage, Object errorDetails) {
|
||||
checkDone();
|
||||
reply.reply(codec.encodeErrorEnvelope(
|
||||
errorCode, errorMessage, errorDetails));
|
||||
done = true;
|
||||
try {
|
||||
reply.reply(codec.encodeErrorEnvelope(
|
||||
errorCode, errorMessage, errorDetails));
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG + name, "Failed to encode error result", e);
|
||||
reply.reply(codec.encodeErrorEnvelope("encode", e.getMessage(), null));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notImplemented() {
|
||||
checkDone();
|
||||
reply.reply(null);
|
||||
done = true;
|
||||
}
|
||||
|
||||
private void checkDone() {
|
||||
if (done) {
|
||||
throw new IllegalStateException("Call result already provided");
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG + name, "Failed to handle method call", e);
|
||||
reply.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
|
||||
reply.reply(codec.encodeErrorEnvelope("uncaught", e.getMessage(), null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* An Android view containing a Flutter app.
|
||||
@ -607,8 +608,12 @@ public class FlutterView extends SurfaceView
|
||||
final ByteBuffer buffer = (message == null ? null : ByteBuffer.wrap(message));
|
||||
handler.onMessage(buffer,
|
||||
new BinaryReply() {
|
||||
private final AtomicBoolean done = new AtomicBoolean(false);
|
||||
@Override
|
||||
public void reply(ByteBuffer reply) {
|
||||
if (done.getAndSet(true)) {
|
||||
throw new IllegalStateException("Reply already submitted");
|
||||
}
|
||||
if (reply == null) {
|
||||
nativeInvokePlatformMessageEmptyResponseCallback(mNativePlatformView,
|
||||
replyId);
|
||||
@ -637,7 +642,7 @@ public class FlutterView extends SurfaceView
|
||||
try {
|
||||
callback.reply(reply == null ? null : ByteBuffer.wrap(reply));
|
||||
} catch (Exception ex) {
|
||||
Log.e(TAG, "Uncaught exception in binary message handler reply", ex);
|
||||
Log.e(TAG, "Uncaught exception in binary message reply handler", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user