EventChannel.java 9.8 KB
Newer Older
M
Michael Goderbauer 已提交
1
// Copyright 2013 The Flutter Authors. All rights reserved.
2 3 4 5 6 7
// 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 android.util.Log;
8 9

import io.flutter.BuildConfig;
10 11
import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler;
import io.flutter.plugin.common.BinaryMessenger.BinaryReply;
12 13

import java.nio.ByteBuffer;
14
import java.util.concurrent.atomic.AtomicBoolean;
15 16 17 18 19 20
import java.util.concurrent.atomic.AtomicReference;

/**
 * A named channel for communicating with the Flutter application using asynchronous
 * event streams.
 *
21
 * <p>Incoming requests for event stream setup are decoded from binary on receipt, and
22 23
 * 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
24 25 26 27 28
 * 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>
29
 *
30 31
 * <p>The logical identity of the channel is given by its name. Identically named channels will interfere
 * with each other's communication.</p>
32
 */
33 34
public final class EventChannel {
    private static final String TAG = "EventChannel#";
35

36
    private final BinaryMessenger messenger;
37 38 39 40
    private final String name;
    private final MethodCodec codec;

    /**
41 42
     * Creates a new channel associated with the specified {@link BinaryMessenger}
     * and with the specified name and the standard {@link MethodCodec}.
43
     *
44
     * @param messenger a {@link BinaryMessenger}.
45 46
     * @param name a channel name String.
     */
47 48
    public EventChannel(BinaryMessenger messenger, String name) {
        this(messenger, name, StandardMethodCodec.INSTANCE);
49 50 51
    }

    /**
52 53
     * Creates a new channel associated with the specified {@link BinaryMessenger}
     * and with the specified name and {@link MethodCodec}.
54
     *
55
     * @param messenger a {@link BinaryMessenger}.
56 57 58
     * @param name a channel name String.
     * @param codec a {@link MessageCodec}.
     */
59
    public EventChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
60 61
        if (BuildConfig.DEBUG) {
            if (messenger == null) {
62
                Log.e(TAG, "Parameter messenger must not be null.");
63 64
            }
            if (name == null) {
65
                Log.e(TAG, "Parameter name must not be null.");
66 67
            }
            if (codec == null) {
68
                Log.e(TAG, "Parameter codec must not be null.");
69 70
            }
        }
71
        this.messenger = messenger;
72 73 74 75 76 77 78
        this.name = name;
        this.codec = codec;
    }

    /**
     * Registers a stream handler on this channel.
     *
79 80 81 82
     * <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>
83 84 85 86
     *
     * @param handler a {@link StreamHandler}, or null to deregister.
     */
    public void setStreamHandler(final StreamHandler handler) {
87
        messenger.setMessageHandler(name, handler == null ? null : new IncomingStreamRequestHandler(handler));
88 89 90
    }

    /**
91 92 93 94 95 96 97 98
     * 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>
99
     */
100
    public interface StreamHandler {
101
        /**
102
         * Handles a request to set up an event stream.
103
         *
104 105
         * <p>Any uncaught exception thrown by this method will be caught by the channel
         * implementation and logged. An error result message will be sent back to Flutter.</p>
106 107 108
         *
         * @param arguments stream configuration arguments, possibly null.
         * @param events an {@link EventSink} for emitting events to the Flutter receiver.
109
         */
110
        void onListen(Object arguments, EventSink events);
111 112

        /**
113
         * Handles a request to tear down the most recently created event stream.
114
         *
115 116
         * <p>Any uncaught exception thrown by this method will be caught by the channel
         * implementation and logged. An error result message will be sent back to Flutter.</p>
117
         *
118 119 120 121 122
         * <p>The channel implementation may call this method with null arguments
         * to separate a pair of two consecutive set up requests. Such request pairs
         * may occur during Flutter hot restart. Any uncaught exception thrown
         * in this situation will be logged without notifying Flutter.</p>
         *
123
         * @param arguments stream configuration arguments, possibly null.
124
         */
125
        void onCancel(Object arguments);
126 127 128
    }

    /**
129 130 131 132
     * 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).
133
     */
134
    public interface EventSink {
135
        /**
136
         * Consumes a successful event.
137
         *
138
         * @param event the event, possibly null.
139
         */
140
        void success(Object event);
141 142

        /**
143
         * Consumes an error event.
144
         *
145 146 147
         * @param errorCode an error code String.
         * @param errorMessage a human-readable error message String, possibly null.
         * @param errorDetails error details, possibly null
148
         */
149 150 151 152 153 154 155
        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();
156 157
    }

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

162
        IncomingStreamRequestHandler(StreamHandler handler) {
163 164 165 166
            this.handler = handler;
        }

        @Override
167
        public void onMessage(ByteBuffer message, final BinaryReply reply) {
168 169 170 171 172 173 174
            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);
175 176 177
            }
        }

178
        private void onListen(Object arguments, BinaryReply callback) {
179
            final EventSink eventSink = new EventSinkImplementation();
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
            final EventSink oldSink = activeSink.getAndSet(eventSink);
            if (oldSink != null) {
              // Repeated calls to onListen may happen during hot restart.
              // We separate them with a call to onCancel.
              try {
                  handler.onCancel(null);
              } catch (RuntimeException e) {
                  Log.e(TAG + name, "Failed to close existing event stream", e);
              }
            }
            try {
                handler.onListen(arguments, eventSink);
                callback.reply(codec.encodeSuccessEnvelope(null));
            } catch (RuntimeException e) {
                activeSink.set(null);
                Log.e(TAG + name, "Failed to open event stream", e);
                callback.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
197 198 199
            }
        }

200
        private void onCancel(Object arguments, BinaryReply callback) {
201 202 203 204
            final EventSink oldSink = activeSink.getAndSet(null);
            if (oldSink != null) {
                try {
                    handler.onCancel(arguments);
205
                    callback.reply(codec.encodeSuccessEnvelope(null));
206
                } catch (RuntimeException e) {
207
                    Log.e(TAG + name, "Failed to close event stream", e);
208
                    callback.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
209 210
                }
            } else {
211
                callback.reply(codec.encodeErrorEnvelope("error", "No active stream to cancel", null));
212 213 214 215
            }
        }

        private final class EventSinkImplementation implements EventSink {
216 217
             final AtomicBoolean hasEnded = new AtomicBoolean(false);

218 219
             @Override
             public void success(Object event) {
220
                 if (hasEnded.get() || activeSink.get() != this) {
221 222
                     return;
                 }
223
                 EventChannel.this.messenger.send(name, codec.encodeSuccessEnvelope(event));
224 225 226
             }

             @Override
227 228
             public void error(String errorCode, String errorMessage, Object errorDetails) {
                 if (hasEnded.get() || activeSink.get() != this) {
229 230
                     return;
                 }
231 232 233
                 EventChannel.this.messenger.send(
                     name,
                     codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
234 235 236 237
             }

             @Override
             public void endOfStream() {
238
                 if (hasEnded.getAndSet(true) || activeSink.get() != this) {
239 240
                     return;
                 }
241
                 EventChannel.this.messenger.send(name, null);
242 243 244 245
             }
         }
    }
}