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.embedding.engine.dart; 6 7 import android.support.annotation.NonNull; 8 import android.support.annotation.Nullable; 9 import android.support.annotation.UiThread; 10 11 import java.nio.ByteBuffer; 12 import java.util.HashMap; 13 import java.util.Map; 14 import java.util.concurrent.atomic.AtomicBoolean; 15 16 import io.flutter.Log; 17 import io.flutter.embedding.engine.FlutterJNI; 18 import io.flutter.plugin.common.BinaryMessenger; 19 20 /** 21 * Message conduit for 2-way communication between Android and Dart. 22 * <p> 23 * WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE. 24 * IF YOU USE IT, WE WILL BREAK YOU. 25 * <p> 26 * See {@link BinaryMessenger}, which sends messages from Android to Dart 27 * <p> 28 * See {@link PlatformMessageHandler}, which handles messages to Android from Dart 29 */ 30 class DartMessenger implements BinaryMessenger, PlatformMessageHandler { 31 private static final String TAG = "DartMessenger"; 32 33 @NonNull 34 private final FlutterJNI flutterJNI; 35 @NonNull 36 private final Map<String, BinaryMessenger.BinaryMessageHandler> messageHandlers; 37 @NonNull 38 private final Map<Integer, BinaryMessenger.BinaryReply> pendingReplies; 39 private int nextReplyId = 1; 40 DartMessenger(@onNull FlutterJNI flutterJNI)41 DartMessenger(@NonNull FlutterJNI flutterJNI) { 42 this.flutterJNI = flutterJNI; 43 this.messageHandlers = new HashMap<>(); 44 this.pendingReplies = new HashMap<>(); 45 } 46 47 @Override setMessageHandler(@onNull String channel, @Nullable BinaryMessenger.BinaryMessageHandler handler)48 public void setMessageHandler(@NonNull String channel, @Nullable BinaryMessenger.BinaryMessageHandler handler) { 49 if (handler == null) { 50 Log.v(TAG, "Removing handler for channel '" + channel + "'"); 51 messageHandlers.remove(channel); 52 } else { 53 Log.v(TAG, "Setting handler for channel '" + channel + "'"); 54 messageHandlers.put(channel, handler); 55 } 56 } 57 58 @Override 59 @UiThread send(@onNull String channel, @NonNull ByteBuffer message)60 public void send(@NonNull String channel, @NonNull ByteBuffer message) { 61 Log.v(TAG, "Sending message over channel '" + channel + "'"); 62 send(channel, message, null); 63 } 64 65 @Override send( @onNull String channel, @Nullable ByteBuffer message, @Nullable BinaryMessenger.BinaryReply callback )66 public void send( 67 @NonNull String channel, 68 @Nullable ByteBuffer message, 69 @Nullable BinaryMessenger.BinaryReply callback 70 ) { 71 Log.v(TAG, "Sending message with callback over channel '" + channel + "'"); 72 int replyId = 0; 73 if (callback != null) { 74 replyId = nextReplyId++; 75 pendingReplies.put(replyId, callback); 76 } 77 if (message == null) { 78 flutterJNI.dispatchEmptyPlatformMessage(channel, replyId); 79 } else { 80 flutterJNI.dispatchPlatformMessage(channel, message, message.position(), replyId); 81 } 82 } 83 84 @Override handleMessageFromDart( @onNull final String channel, @Nullable byte[] message, final int replyId )85 public void handleMessageFromDart( 86 @NonNull final String channel, 87 @Nullable byte[] message, 88 final int replyId 89 ) { 90 Log.v(TAG, "Received message from Dart over channel '" + channel + "'"); 91 BinaryMessenger.BinaryMessageHandler handler = messageHandlers.get(channel); 92 if (handler != null) { 93 try { 94 Log.v(TAG, "Deferring to registered handler to process message."); 95 final ByteBuffer buffer = (message == null ? null : ByteBuffer.wrap(message)); 96 handler.onMessage(buffer, new Reply(flutterJNI, replyId)); 97 } catch (Exception ex) { 98 Log.e(TAG, "Uncaught exception in binary message listener", ex); 99 flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId); 100 } 101 } else { 102 Log.v(TAG, "No registered handler for message. Responding to Dart with empty reply message."); 103 flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId); 104 } 105 } 106 107 @Override handlePlatformMessageResponse(int replyId, @Nullable byte[] reply)108 public void handlePlatformMessageResponse(int replyId, @Nullable byte[] reply) { 109 Log.v(TAG, "Received message reply from Dart."); 110 BinaryMessenger.BinaryReply callback = pendingReplies.remove(replyId); 111 if (callback != null) { 112 try { 113 Log.v(TAG, "Invoking registered callback for reply from Dart."); 114 callback.reply(reply == null ? null : ByteBuffer.wrap(reply)); 115 } catch (Exception ex) { 116 Log.e(TAG, "Uncaught exception in binary message reply handler", ex); 117 } 118 } 119 } 120 121 /** 122 * Returns the number of pending channel callback replies. 123 * 124 * <p>When sending messages to the Flutter application using {@link BinaryMessenger#send(String, 125 * ByteBuffer, io.flutter.plugin.common.BinaryMessenger.BinaryReply)}, developers can optionally 126 * specify a reply callback if they expect a reply from the Flutter application. 127 * 128 * <p>This method tracks all the pending callbacks that are waiting for response, and is supposed 129 * to be called from the main thread (as other methods). Calling from a different thread could 130 * possibly capture an indeterministic internal state, so don't do it. 131 */ 132 @UiThread getPendingChannelResponseCount()133 public int getPendingChannelResponseCount() { 134 return pendingReplies.size(); 135 } 136 137 private static class Reply implements BinaryMessenger.BinaryReply { 138 @NonNull 139 private final FlutterJNI flutterJNI; 140 private final int replyId; 141 private final AtomicBoolean done = new AtomicBoolean(false); 142 Reply(@onNull FlutterJNI flutterJNI, int replyId)143 Reply(@NonNull FlutterJNI flutterJNI, int replyId) { 144 this.flutterJNI = flutterJNI; 145 this.replyId = replyId; 146 } 147 148 @Override reply(@ullable ByteBuffer reply)149 public void reply(@Nullable ByteBuffer reply) { 150 if (done.getAndSet(true)) { 151 throw new IllegalStateException("Reply already submitted"); 152 } 153 if (reply == null) { 154 flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId); 155 } else { 156 flutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position()); 157 } 158 } 159 } 160 } 161