1 // Copyright 2013 The Flutter Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package io.flutter.plugin.common; 6 7 import android.support.annotation.NonNull; 8 import android.support.annotation.Nullable; 9 import android.support.annotation.UiThread; 10 import android.util.Log; 11 12 import io.flutter.BuildConfig; 13 import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler; 14 import io.flutter.plugin.common.BinaryMessenger.BinaryReply; 15 16 import java.nio.ByteBuffer; 17 18 /** 19 * A named channel for communicating with the Flutter application using asynchronous 20 * method calls. 21 * 22 * <p>Incoming method calls are decoded from binary on receipt, and Java results are encoded 23 * into binary before being transmitted back to Flutter. The {@link MethodCodec} used must be 24 * compatible with the one used by the Flutter application. This can be achieved 25 * by creating a 26 * <a href="https://docs.flutter.io/flutter/services/MethodChannel-class.html">MethodChannel</a> 27 * counterpart of this channel on the Dart side. The Java type of method call arguments and results is 28 * {@code Object}, but only values supported by the specified {@link MethodCodec} can be used.</p> 29 * 30 * <p>The logical identity of the channel is given by its name. Identically named channels will interfere 31 * with each other's communication.</p> 32 */ 33 public final class MethodChannel { 34 private static final String TAG = "MethodChannel#"; 35 36 private final BinaryMessenger messenger; 37 private final String name; 38 private final MethodCodec codec; 39 40 /** 41 * Creates a new channel associated with the specified {@link BinaryMessenger} 42 * and with the specified name and the standard {@link MethodCodec}. 43 * 44 * @param messenger a {@link BinaryMessenger}. 45 * @param name a channel name String. 46 */ MethodChannel(BinaryMessenger messenger, String name)47 public MethodChannel(BinaryMessenger messenger, String name) { 48 this(messenger, name, StandardMethodCodec.INSTANCE); 49 } 50 51 /** 52 * Creates a new channel associated with the specified {@link BinaryMessenger} and with the 53 * specified name and {@link MethodCodec}. 54 * 55 * @param messenger a {@link BinaryMessenger}. 56 * @param name a channel name String. 57 * @param codec a {@link MessageCodec}. 58 */ MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec)59 public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) { 60 if (BuildConfig.DEBUG) { 61 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 this.name = name; 73 this.codec = codec; 74 } 75 76 /** 77 * Invokes a method on this channel, expecting no result. 78 * 79 * @param method the name String of the method. 80 * @param arguments the arguments for the invocation, possibly null. 81 */ 82 @UiThread invokeMethod(@onNull String method, @Nullable Object arguments)83 public void invokeMethod(@NonNull String method, @Nullable Object arguments) { 84 invokeMethod(method, arguments, null); 85 } 86 87 /** 88 * Invokes a method on this channel, optionally expecting a result. 89 * 90 * <p>Any uncaught exception thrown by the result callback will be caught and logged.</p> 91 * 92 * @param method the name String of the method. 93 * @param arguments the arguments for the invocation, possibly null. 94 * @param callback a {@link Result} callback for the invocation result, or null. 95 */ 96 @UiThread invokeMethod(String method, @Nullable Object arguments, Result callback)97 public void invokeMethod(String method, @Nullable Object arguments, Result callback) { 98 messenger.send(name, codec.encodeMethodCall(new MethodCall(method, arguments)), 99 callback == null ? null : new IncomingResultHandler(callback)); 100 } 101 102 /** 103 * Registers a method call handler on this channel. 104 * 105 * <p>Overrides any existing handler registration for (the name of) this channel.</p> 106 * 107 * <p>If no handler has been registered, any incoming method call on this channel will be handled 108 * silently by sending a null reply. This results in a 109 * <a href="https://docs.flutter.io/flutter/services/MissingPluginException-class.html">MissingPluginException</a> 110 * on the Dart side, unless an 111 * <a href="https://docs.flutter.io/flutter/services/OptionalMethodChannel-class.html">OptionalMethodChannel</a> 112 * is used.</p> 113 * 114 * @param handler a {@link MethodCallHandler}, or null to deregister. 115 */ 116 @UiThread setMethodCallHandler(final @Nullable MethodCallHandler handler)117 public void setMethodCallHandler(final @Nullable MethodCallHandler handler) { 118 messenger.setMessageHandler(name, 119 handler == null ? null : new IncomingMethodCallHandler(handler)); 120 } 121 122 /** 123 * A handler of incoming method calls. 124 */ 125 public interface MethodCallHandler { 126 /** 127 * Handles the specified method call received from Flutter. 128 * 129 * <p>Handler implementations must submit a result for all incoming calls, by making a single call 130 * on the given {@link Result} callback. Failure to do so will result in lingering Flutter result 131 * handlers. The result may be submitted asynchronously. Calls to unknown or unimplemented methods 132 * should be handled using {@link Result#notImplemented()}.</p> 133 * 134 * <p>Any uncaught exception thrown by this method will be caught by the channel implementation and 135 * logged, and an error result will be sent back to Flutter.</p> 136 * 137 * <p>The handler is called on the platform thread (Android main thread). For more details see 138 * <a href="https://github.com/flutter/engine/wiki/Threading-in-the-Flutter-Engine">Threading in the Flutter 139 * Engine</a>.</p> 140 * 141 * @param call A {@link MethodCall}. 142 * @param result A {@link Result} used for submitting the result of the call. 143 */ 144 @UiThread onMethodCall(@onNull MethodCall call, @NonNull Result result)145 void onMethodCall(@NonNull MethodCall call, @NonNull Result result); 146 } 147 148 /** 149 * Method call result callback. Supports dual use: Implementations of methods 150 * to be invoked by Flutter act as clients of this interface for sending results 151 * back to Flutter. Invokers of Flutter methods provide implementations of this 152 * interface for handling results received from Flutter. 153 * 154 * <p>All methods of this class must be called on the platform thread (Android main thread). For more details see 155 * <a href="https://github.com/flutter/engine/wiki/Threading-in-the-Flutter-Engine">Threading in the Flutter 156 * Engine</a>.</p> 157 */ 158 public interface Result { 159 /** 160 * Handles a successful result. 161 * 162 * @param result The result, possibly null. 163 */ 164 @UiThread success(@ullable Object result)165 void success(@Nullable Object result); 166 167 /** 168 * Handles an error result. 169 * 170 * @param errorCode An error code String. 171 * @param errorMessage A human-readable error message String, possibly null. 172 * @param errorDetails Error details, possibly null 173 */ 174 @UiThread error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails)175 void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails); 176 177 /** 178 * Handles a call to an unimplemented method. 179 */ 180 @UiThread notImplemented()181 void notImplemented(); 182 } 183 184 private final class IncomingResultHandler implements BinaryReply { 185 private final Result callback; 186 IncomingResultHandler(Result callback)187 IncomingResultHandler(Result callback) { 188 this.callback = callback; 189 } 190 191 @Override 192 @UiThread reply(ByteBuffer reply)193 public void reply(ByteBuffer reply) { 194 try { 195 if (reply == null) { 196 callback.notImplemented(); 197 } else { 198 try { 199 callback.success(codec.decodeEnvelope(reply)); 200 } catch (FlutterException e) { 201 callback.error(e.code, e.getMessage(), e.details); 202 } 203 } 204 } catch (RuntimeException e) { 205 Log.e(TAG + name, "Failed to handle method call result", e); 206 } 207 } 208 } 209 210 private final class IncomingMethodCallHandler implements BinaryMessageHandler { 211 private final MethodCallHandler handler; 212 IncomingMethodCallHandler(MethodCallHandler handler)213 IncomingMethodCallHandler(MethodCallHandler handler) { 214 this.handler = handler; 215 } 216 217 @Override 218 @UiThread onMessage(ByteBuffer message, final BinaryReply reply)219 public void onMessage(ByteBuffer message, final BinaryReply reply) { 220 final MethodCall call = codec.decodeMethodCall(message); 221 try { 222 handler.onMethodCall(call, new Result() { 223 @Override 224 public void success(Object result) { 225 reply.reply(codec.encodeSuccessEnvelope(result)); 226 } 227 228 @Override 229 public void error(String errorCode, String errorMessage, Object errorDetails) { 230 reply.reply(codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails)); 231 } 232 233 @Override 234 public void notImplemented() { 235 reply.reply(null); 236 } 237 }); 238 } catch (RuntimeException e) { 239 Log.e(TAG + name, "Failed to handle method call", e); 240 reply.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null)); 241 } 242 } 243 } 244 } 245