blob: 7e6b9706f22ef40ae61dec530ae34d03573f0741 [file] [log] [blame]
// 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.embedding.engine.dart;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.plugin.common.BinaryMessenger;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Message conduit for 2-way communication between Android and Dart.
*
* <p>See {@link BinaryMessenger}, which sends messages from Android to Dart
*
* <p>See {@link PlatformMessageHandler}, which handles messages to Android from Dart
*/
class DartMessenger implements BinaryMessenger, PlatformMessageHandler {
private static final String TAG = "DartMessenger";
@NonNull private final FlutterJNI flutterJNI;
@NonNull private final ConcurrentHashMap<String, HandlerInfo> messageHandlers;
@NonNull private final Map<Integer, BinaryMessenger.BinaryReply> pendingReplies;
private int nextReplyId = 1;
@NonNull private final DartMessengerTaskQueue platformTaskQueue = new PlatformTaskQueue();
@NonNull private WeakHashMap<TaskQueue, DartMessengerTaskQueue> createdTaskQueues;
@NonNull private TaskQueueFactory taskQueueFactory;
DartMessenger(@NonNull FlutterJNI flutterJNI, @NonNull TaskQueueFactory taskQueueFactory) {
this.flutterJNI = flutterJNI;
this.messageHandlers = new ConcurrentHashMap<>();
this.pendingReplies = new HashMap<>();
this.createdTaskQueues = new WeakHashMap<TaskQueue, DartMessengerTaskQueue>();
this.taskQueueFactory = taskQueueFactory;
}
DartMessenger(@NonNull FlutterJNI flutterJNI) {
this(flutterJNI, new DefaultTaskQueueFactory());
}
private static class TaskQueueToken implements TaskQueue {}
interface DartMessengerTaskQueue {
void dispatch(@NonNull Runnable runnable);
}
interface TaskQueueFactory {
DartMessengerTaskQueue makeBackgroundTaskQueue();
}
private static class DefaultTaskQueueFactory implements TaskQueueFactory {
public DartMessengerTaskQueue makeBackgroundTaskQueue() {
return new DefaultTaskQueue();
}
}
private static class HandlerInfo {
@NonNull public final BinaryMessenger.BinaryMessageHandler handler;
@Nullable public final DartMessengerTaskQueue taskQueue;
HandlerInfo(
@NonNull BinaryMessenger.BinaryMessageHandler handler,
@Nullable DartMessengerTaskQueue taskQueue) {
this.handler = handler;
this.taskQueue = taskQueue;
}
}
private static class DefaultTaskQueue implements DartMessengerTaskQueue {
@NonNull private final ExecutorService executor;
DefaultTaskQueue() {
// TODO(gaaclarke): Use a shared thread pool with serial queues instead of
// making a thread for each TaskQueue.
ThreadFactory threadFactory =
(Runnable runnable) -> {
return new Thread(runnable, "DartMessenger.DefaultTaskQueue");
};
this.executor = Executors.newSingleThreadExecutor(threadFactory);
}
@Override
public void dispatch(@NonNull Runnable runnable) {
executor.execute(runnable);
}
}
@Override
public TaskQueue makeBackgroundTaskQueue() {
DartMessengerTaskQueue taskQueue = taskQueueFactory.makeBackgroundTaskQueue();
TaskQueueToken token = new TaskQueueToken();
createdTaskQueues.put(token, taskQueue);
return token;
}
@Override
public void setMessageHandler(
@NonNull String channel,
@Nullable BinaryMessenger.BinaryMessageHandler handler,
@Nullable TaskQueue taskQueue) {
if (handler == null) {
Log.v(TAG, "Removing handler for channel '" + channel + "'");
messageHandlers.remove(channel);
} else {
DartMessengerTaskQueue dartMessengerTaskQueue = null;
if (taskQueue != null) {
dartMessengerTaskQueue = createdTaskQueues.get(taskQueue);
if (dartMessengerTaskQueue == null) {
throw new IllegalArgumentException(
"Unrecognized TaskQueue, use BinaryMessenger to create your TaskQueue (ex makeBackgroundTaskQueue).");
}
}
Log.v(TAG, "Setting handler for channel '" + channel + "'");
messageHandlers.put(channel, new HandlerInfo(handler, dartMessengerTaskQueue));
}
}
@Override
@UiThread
public void send(@NonNull String channel, @NonNull ByteBuffer message) {
Log.v(TAG, "Sending message over channel '" + channel + "'");
send(channel, message, null);
}
@Override
public void send(
@NonNull String channel,
@Nullable ByteBuffer message,
@Nullable BinaryMessenger.BinaryReply callback) {
Log.v(TAG, "Sending message with callback over channel '" + channel + "'");
int replyId = nextReplyId++;
if (callback != null) {
pendingReplies.put(replyId, callback);
}
if (message == null) {
flutterJNI.dispatchEmptyPlatformMessage(channel, replyId);
} else {
flutterJNI.dispatchPlatformMessage(channel, message, message.position(), replyId);
}
}
private void invokeHandler(
@Nullable HandlerInfo handlerInfo, @Nullable ByteBuffer message, final int replyId) {
// Called from any thread.
if (handlerInfo != null) {
try {
Log.v(TAG, "Deferring to registered handler to process message.");
handlerInfo.handler.onMessage(message, new Reply(flutterJNI, replyId));
} catch (Exception ex) {
Log.e(TAG, "Uncaught exception in binary message listener", ex);
flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
} catch (Error err) {
handleError(err);
}
} else {
Log.v(TAG, "No registered handler for message. Responding to Dart with empty reply message.");
flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
}
@Override
public void handleMessageFromDart(
@NonNull final String channel,
@Nullable ByteBuffer message,
final int replyId,
long messageData) {
// Called from the ui thread.
Log.v(TAG, "Received message from Dart over channel '" + channel + "'");
@Nullable final HandlerInfo handlerInfo = messageHandlers.get(channel);
@Nullable
final DartMessengerTaskQueue taskQueue = (handlerInfo != null) ? handlerInfo.taskQueue : null;
Runnable myRunnable =
() -> {
try {
invokeHandler(handlerInfo, message, replyId);
if (message != null && message.isDirect()) {
// This ensures that if a user retains an instance to the ByteBuffer and it happens to
// be direct they will get a deterministic error.
message.limit(0);
}
} finally {
// This is deleting the data underneath the message object.
flutterJNI.cleanupMessageData(messageData);
}
};
@NonNull
final DartMessengerTaskQueue nonnullTaskQueue =
taskQueue == null ? platformTaskQueue : taskQueue;
nonnullTaskQueue.dispatch(myRunnable);
}
@Override
public void handlePlatformMessageResponse(int replyId, @Nullable ByteBuffer reply) {
Log.v(TAG, "Received message reply from Dart.");
BinaryMessenger.BinaryReply callback = pendingReplies.remove(replyId);
if (callback != null) {
try {
Log.v(TAG, "Invoking registered callback for reply from Dart.");
callback.reply(reply);
if (reply != null && reply.isDirect()) {
// This ensures that if a user retains an instance to the ByteBuffer and it happens to
// be direct they will get a deterministic error.
reply.limit(0);
}
} catch (Exception ex) {
Log.e(TAG, "Uncaught exception in binary message reply handler", ex);
} catch (Error err) {
handleError(err);
}
}
}
/**
* Returns the number of pending channel callback replies.
*
* <p>When sending messages to the Flutter application using {@link BinaryMessenger#send(String,
* ByteBuffer, io.flutter.plugin.common.BinaryMessenger.BinaryReply)}, developers can optionally
* specify a reply callback if they expect a reply from the Flutter application.
*
* <p>This method tracks all the pending callbacks that are waiting for response, and is supposed
* to be called from the main thread (as other methods). Calling from a different thread could
* possibly capture an indeterministic internal state, so don't do it.
*/
@UiThread
public int getPendingChannelResponseCount() {
return pendingReplies.size();
}
// Handles `Error` objects which are not supposed to be caught.
//
// We forward them to the thread's uncaught exception handler if there is one. If not, they
// are rethrown.
private static void handleError(Error err) {
Thread currentThread = Thread.currentThread();
if (currentThread.getUncaughtExceptionHandler() == null) {
throw err;
}
currentThread.getUncaughtExceptionHandler().uncaughtException(currentThread, err);
}
static class Reply implements BinaryMessenger.BinaryReply {
@NonNull private final FlutterJNI flutterJNI;
private final int replyId;
private final AtomicBoolean done = new AtomicBoolean(false);
Reply(@NonNull FlutterJNI flutterJNI, int replyId) {
this.flutterJNI = flutterJNI;
this.replyId = replyId;
}
@Override
public void reply(@Nullable ByteBuffer reply) {
if (done.getAndSet(true)) {
throw new IllegalStateException("Reply already submitted");
}
if (reply == null) {
flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
} else {
flutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position());
}
}
}
}