Android Background Platform Channels (#29147)
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index 8e2f603..af42c73 100755
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -708,6 +708,7 @@
FILE: ../../../flutter/shell/common/pipeline.cc
FILE: ../../../flutter/shell/common/pipeline.h
FILE: ../../../flutter/shell/common/pipeline_unittests.cc
+FILE: ../../../flutter/shell/common/platform_message_handler.h
FILE: ../../../flutter/shell/common/platform_view.cc
FILE: ../../../flutter/shell/common/platform_view.h
FILE: ../../../flutter/shell/common/pointer_data_dispatcher.cc
@@ -841,6 +842,7 @@
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java
+FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/PlatformTaskQueue.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/DeferredComponentManager.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java
@@ -938,6 +940,8 @@
FILE: ../../../flutter/shell/platform/android/jni/platform_view_android_jni.cc
FILE: ../../../flutter/shell/platform/android/jni/platform_view_android_jni.h
FILE: ../../../flutter/shell/platform/android/library_loader.cc
+FILE: ../../../flutter/shell/platform/android/platform_message_handler_android.cc
+FILE: ../../../flutter/shell/platform/android/platform_message_handler_android.h
FILE: ../../../flutter/shell/platform/android/platform_message_response_android.cc
FILE: ../../../flutter/shell/platform/android/platform_message_response_android.h
FILE: ../../../flutter/shell/platform/android/platform_view_android.cc
diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn
index b8d0010..0a4ba22 100644
--- a/shell/common/BUILD.gn
+++ b/shell/common/BUILD.gn
@@ -73,6 +73,7 @@
"engine.h",
"pipeline.cc",
"pipeline.h",
+ "platform_message_handler.h",
"platform_view.cc",
"platform_view.h",
"pointer_data_dispatcher.cc",
diff --git a/shell/common/platform_message_handler.h b/shell/common/platform_message_handler.h
new file mode 100644
index 0000000..8e3f77e
--- /dev/null
+++ b/shell/common/platform_message_handler.h
@@ -0,0 +1,39 @@
+// 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.
+
+#ifndef SHELL_COMMON_PLATFORM_MESSAGE_HANDLER_H_
+#define SHELL_COMMON_PLATFORM_MESSAGE_HANDLER_H_
+
+#include <memory>
+
+#include "flutter/lib/ui/window/platform_message.h"
+
+namespace flutter {
+
+/// An interface over the ability to handle PlatformMessages that are being sent
+/// from Flutter to the host platform.
+class PlatformMessageHandler {
+ public:
+ virtual ~PlatformMessageHandler() = default;
+
+ /// Ultimately sends the PlatformMessage to the host platform.
+ /// This method is invoked on the ui thread.
+ virtual void HandlePlatformMessage(
+ std::unique_ptr<PlatformMessage> message) = 0;
+
+ /// Performs the return procedure for an associated call to
+ /// HandlePlatformMessage.
+ /// This method should be thread-safe and able to be invoked on any thread.
+ virtual void InvokePlatformMessageResponseCallback(
+ int response_id,
+ std::unique_ptr<fml::Mapping> mapping) = 0;
+
+ /// Performs the return procedure for an associated call to
+ /// HandlePlatformMessage where there is no return value.
+ /// This method should be thread-safe and able to be invoked on any thread.
+ virtual void InvokePlatformMessageEmptyResponseCallback(int response_id) = 0;
+};
+} // namespace flutter
+
+#endif // SHELL_COMMON_PLATFORM_MESSAGE_HANDLER_H_
diff --git a/shell/common/platform_view.cc b/shell/common/platform_view.cc
index cd3fada..2752a6e 100644
--- a/shell/common/platform_view.cc
+++ b/shell/common/platform_view.cc
@@ -184,4 +184,9 @@
return nullptr;
}
+std::shared_ptr<PlatformMessageHandler>
+PlatformView::GetPlatformMessageHandler() const {
+ return nullptr;
+}
+
} // namespace flutter
diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h
index 85e0a4f..b6555f6 100644
--- a/shell/common/platform_view.h
+++ b/shell/common/platform_view.h
@@ -22,6 +22,7 @@
#include "flutter/lib/ui/window/pointer_data_packet.h"
#include "flutter/lib/ui/window/pointer_data_packet_converter.h"
#include "flutter/lib/ui/window/viewport_metrics.h"
+#include "flutter/shell/common/platform_message_handler.h"
#include "flutter/shell/common/pointer_data_dispatcher.h"
#include "flutter/shell/common/vsync_waiter.h"
#include "third_party/skia/include/core/SkSize.h"
@@ -810,6 +811,17 @@
virtual std::unique_ptr<SnapshotSurfaceProducer>
CreateSnapshotSurfaceProducer();
+ //--------------------------------------------------------------------------
+ /// @brief Specifies a delegate that will receive PlatformMessages from
+ /// Flutter to the host platform.
+ ///
+ /// @details If this returns `null` that means PlatformMessages should be sent
+ /// to the PlatformView. That is to protect legacy behavior, any embedder
+ /// that wants to support executing Platform Channel handlers on background
+ /// threads should be returing a thread-safe PlatformMessageHandler instead.
+ virtual std::shared_ptr<PlatformMessageHandler> GetPlatformMessageHandler()
+ const;
+
protected:
PlatformView::Delegate& delegate_;
const TaskRunners task_runners_;
diff --git a/shell/common/shell.cc b/shell/common/shell.cc
index 24061c2..df301e7 100644
--- a/shell/common/shell.cc
+++ b/shell/common/shell.cc
@@ -625,6 +625,7 @@
}
platform_view_ = std::move(platform_view);
+ platform_message_handler_ = platform_view_->GetPlatformMessageHandler();
engine_ = std::move(engine);
rasterizer_ = std::move(rasterizer);
io_manager_ = std::move(io_manager);
@@ -1192,13 +1193,17 @@
return;
}
- task_runners_.GetPlatformTaskRunner()->PostTask(
- fml::MakeCopyable([view = platform_view_->GetWeakPtr(),
- message = std::move(message)]() mutable {
- if (view) {
- view->HandlePlatformMessage(std::move(message));
- }
- }));
+ if (platform_message_handler_) {
+ platform_message_handler_->HandlePlatformMessage(std::move(message));
+ } else {
+ task_runners_.GetPlatformTaskRunner()->PostTask(
+ fml::MakeCopyable([view = platform_view_->GetWeakPtr(),
+ message = std::move(message)]() mutable {
+ if (view) {
+ view->HandlePlatformMessage(std::move(message));
+ }
+ }));
+ }
}
void Shell::HandleEngineSkiaMessage(std::unique_ptr<PlatformMessage> message) {
@@ -1867,4 +1872,9 @@
return fml::TimePoint::Now();
}
+const std::shared_ptr<PlatformMessageHandler>&
+Shell::GetPlatformMessageHandler() const {
+ return platform_message_handler_;
+}
+
} // namespace flutter
diff --git a/shell/common/shell.h b/shell/common/shell.h
index 9fbdd5b..3884299 100644
--- a/shell/common/shell.h
+++ b/shell/common/shell.h
@@ -395,6 +395,12 @@
/// @see `CreateCompatibleGenerator`
void RegisterImageDecoder(ImageGeneratorFactory factory, int32_t priority);
+ //----------------------------------------------------------------------------
+ /// @brief Returns the delegate object that handles PlatformMessage's from
+ /// Flutter to the host platform (and its responses).
+ const std::shared_ptr<PlatformMessageHandler>& GetPlatformMessageHandler()
+ const;
+
private:
using ServiceProtocolHandler =
std::function<bool(const ServiceProtocol::Handler::ServiceProtocolMap&,
@@ -412,6 +418,7 @@
std::unique_ptr<ShellIOManager> io_manager_; // on IO task runner
std::shared_ptr<fml::SyncSwitch> is_gpu_disabled_sync_switch_;
std::shared_ptr<VolatilePathTracker> volatile_path_tracker_;
+ std::shared_ptr<PlatformMessageHandler> platform_message_handler_;
fml::WeakPtr<Engine> weak_engine_; // to be shared across threads
fml::TaskRunnerAffineWeakPtr<Rasterizer>
diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc
index b5cc9c8..b986d58 100644
--- a/shell/common/shell_test.cc
+++ b/shell/common/shell_test.cc
@@ -325,7 +325,8 @@
std::shared_ptr<ShellTestExternalViewEmbedder>
shell_test_external_view_embedder,
bool is_gpu_disabled,
- ShellTestPlatformView::BackendType rendering_backend) {
+ ShellTestPlatformView::BackendType rendering_backend,
+ Shell::CreateCallback<PlatformView> platform_view_create_callback) {
const auto vsync_clock = std::make_shared<ShellTestVsyncClock>();
CreateVsyncWaiter create_vsync_waiter = [&]() {
@@ -338,21 +339,21 @@
}
};
- Shell::CreateCallback<PlatformView> platfrom_view_create_callback =
- [vsync_clock, //
- &create_vsync_waiter, //
- shell_test_external_view_embedder, //
- rendering_backend //
- ](Shell& shell) {
- return ShellTestPlatformView::Create(
- shell, //
- shell.GetTaskRunners(), //
- vsync_clock, //
- std::move(create_vsync_waiter), //
- rendering_backend, //
- shell_test_external_view_embedder //
- );
- };
+ if (!platform_view_create_callback) {
+ platform_view_create_callback = [vsync_clock, //
+ &create_vsync_waiter, //
+ shell_test_external_view_embedder, //
+ rendering_backend //
+ ](Shell& shell) {
+ return ShellTestPlatformView::Create(shell, //
+ shell.GetTaskRunners(), //
+ vsync_clock, //
+ std::move(create_vsync_waiter), //
+ rendering_backend, //
+ shell_test_external_view_embedder //
+ );
+ };
+ }
Shell::CreateCallback<Rasterizer> rasterizer_create_callback =
[](Shell& shell) { return std::make_unique<Rasterizer>(shell); };
@@ -360,7 +361,7 @@
return Shell::Create(flutter::PlatformData(), //
task_runners, //
settings, //
- platfrom_view_create_callback, //
+ platform_view_create_callback, //
rasterizer_create_callback, //
is_gpu_disabled //
);
diff --git a/shell/common/shell_test.h b/shell/common/shell_test.h
index 1524c15..4a41060 100644
--- a/shell/common/shell_test.h
+++ b/shell/common/shell_test.h
@@ -43,7 +43,9 @@
shell_test_external_view_embedder = nullptr,
bool is_gpu_disabled = false,
ShellTestPlatformView::BackendType rendering_backend =
- ShellTestPlatformView::BackendType::kDefaultBackend);
+ ShellTestPlatformView::BackendType::kDefaultBackend,
+ Shell::CreateCallback<PlatformView> platform_view_create_callback =
+ nullptr);
void DestroyShell(std::unique_ptr<Shell> shell);
void DestroyShell(std::unique_ptr<Shell> shell, TaskRunners task_runners);
TaskRunners GetTaskRunnersForFixture();
diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc
index 309cd28..2b0ca11 100644
--- a/shell/common/shell_unittests.cc
+++ b/shell/common/shell_unittests.cc
@@ -45,6 +45,10 @@
namespace flutter {
namespace testing {
+
+using ::testing::_;
+using ::testing::Return;
+
namespace {
class MockPlatformViewDelegate : public PlatformView::Delegate {
MOCK_METHOD1(OnPlatformViewCreated, void(std::unique_ptr<Surface> surface));
@@ -119,6 +123,27 @@
MockPlatformView(MockPlatformViewDelegate& delegate, TaskRunners task_runners)
: PlatformView(delegate, task_runners) {}
MOCK_METHOD0(CreateRenderingSurface, std::unique_ptr<Surface>());
+ MOCK_CONST_METHOD0(GetPlatformMessageHandler,
+ std::shared_ptr<PlatformMessageHandler>());
+};
+
+class MockPlatformMessageHandler : public PlatformMessageHandler {
+ public:
+ MOCK_METHOD1(HandlePlatformMessage,
+ void(std::unique_ptr<PlatformMessage> message));
+ MOCK_METHOD2(InvokePlatformMessageResponseCallback,
+ void(int response_id, std::unique_ptr<fml::Mapping> mapping));
+ MOCK_METHOD1(InvokePlatformMessageEmptyResponseCallback,
+ void(int response_id));
+};
+
+class MockPlatformMessageResponse : public PlatformMessageResponse {
+ public:
+ static fml::RefPtr<MockPlatformMessageResponse> Create() {
+ return fml::AdoptRef(new MockPlatformMessageResponse());
+ }
+ MOCK_METHOD1(Complete, void(std::unique_ptr<fml::Mapping> data));
+ MOCK_METHOD0(CompleteEmpty, void());
};
} // namespace
@@ -3154,7 +3179,52 @@
shell->GetTaskRunners().GetUITaskRunner(),
[&ui_flush_latch]() { ui_flush_latch.Signal(); });
ui_flush_latch.Wait();
+ DestroyShell(std::move(shell));
+}
+TEST_F(ShellTest, UsesPlatformMessageHandler) {
+ TaskRunners task_runners = GetTaskRunnersForFixture();
+ auto settings = CreateSettingsForFixture();
+ MockPlatformViewDelegate platform_view_delegate;
+ auto platform_message_handler =
+ std::make_shared<MockPlatformMessageHandler>();
+ int message_id = 1;
+ EXPECT_CALL(*platform_message_handler, HandlePlatformMessage(_));
+ EXPECT_CALL(*platform_message_handler,
+ InvokePlatformMessageEmptyResponseCallback(message_id));
+ Shell::CreateCallback<PlatformView> platform_view_create_callback =
+ [&platform_view_delegate, task_runners,
+ platform_message_handler](flutter::Shell& shell) {
+ auto result = std::make_unique<MockPlatformView>(platform_view_delegate,
+ task_runners);
+ EXPECT_CALL(*result, GetPlatformMessageHandler())
+ .WillOnce(Return(platform_message_handler));
+ return result;
+ };
+ auto shell = CreateShell(
+ /*settings=*/std::move(settings),
+ /*task_runners=*/task_runners,
+ /*simulate_vsync=*/false,
+ /*shell_test_external_view_embedder=*/nullptr,
+ /*is_gpu_disabled=*/false,
+ /*rendering_backend=*/
+ ShellTestPlatformView::BackendType::kDefaultBackend,
+ /*platform_view_create_callback=*/platform_view_create_callback);
+
+ EXPECT_EQ(platform_message_handler, shell->GetPlatformMessageHandler());
+ PostSync(task_runners.GetUITaskRunner(), [&shell]() {
+ size_t data_size = 4;
+ fml::MallocMapping bytes =
+ fml::MallocMapping(static_cast<uint8_t*>(malloc(data_size)), data_size);
+ fml::RefPtr<MockPlatformMessageResponse> response =
+ MockPlatformMessageResponse::Create();
+ auto message = std::make_unique<PlatformMessage>(
+ /*channel=*/"foo", /*data=*/std::move(bytes), /*response=*/response);
+ (static_cast<Engine::Delegate*>(shell.get()))
+ ->OnEngineHandlePlatformMessage(std::move(message));
+ });
+ shell->GetPlatformMessageHandler()
+ ->InvokePlatformMessageEmptyResponseCallback(message_id);
DestroyShell(std::move(shell));
}
diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn
index 2780a73..862fc40 100644
--- a/shell/platform/android/BUILD.gn
+++ b/shell/platform/android/BUILD.gn
@@ -79,6 +79,8 @@
"flutter_main.cc",
"flutter_main.h",
"library_loader.cc",
+ "platform_message_handler_android.cc",
+ "platform_message_handler_android.h",
"platform_message_response_android.cc",
"platform_message_response_android.h",
"platform_view_android.cc",
@@ -186,6 +188,7 @@
"io/flutter/embedding/engine/dart/DartExecutor.java",
"io/flutter/embedding/engine/dart/DartMessenger.java",
"io/flutter/embedding/engine/dart/PlatformMessageHandler.java",
+ "io/flutter/embedding/engine/dart/PlatformTaskQueue.java",
"io/flutter/embedding/engine/deferredcomponents/DeferredComponentManager.java",
"io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java",
"io/flutter/embedding/engine/loader/ApplicationInfoLoader.java",
diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h
index 9ea264f..062e7e4 100644
--- a/shell/platform/android/android_shell_holder.h
+++ b/shell/platform/android/android_shell_holder.h
@@ -16,6 +16,7 @@
#include "flutter/shell/common/shell.h"
#include "flutter/shell/common/thread_host.h"
#include "flutter/shell/platform/android/jni/platform_view_android_jni.h"
+#include "flutter/shell/platform/android/platform_message_handler_android.h"
#include "flutter/shell/platform/android/platform_view_android.h"
namespace flutter {
@@ -96,6 +97,11 @@
void NotifyLowMemoryWarning();
+ const std::shared_ptr<PlatformMessageHandler>& GetPlatformMessageHandler()
+ const {
+ return shell_->GetPlatformMessageHandler();
+ }
+
private:
const flutter::Settings settings_;
const std::shared_ptr<PlatformViewAndroidJNI> jni_facade_;
diff --git a/shell/platform/android/android_shell_holder_unittests.cc b/shell/platform/android/android_shell_holder_unittests.cc
index 2d3bc3e..85cb352 100644
--- a/shell/platform/android/android_shell_holder_unittests.cc
+++ b/shell/platform/android/android_shell_holder_unittests.cc
@@ -7,6 +7,7 @@
namespace testing {
namespace {
class MockPlatformViewAndroidJNI : public PlatformViewAndroidJNI {
+ public:
MOCK_METHOD2(FlutterViewHandlePlatformMessage,
void(std::unique_ptr<flutter::PlatformMessage> message,
int responseId));
@@ -51,6 +52,15 @@
MOCK_METHOD0(GetDisplayRefreshRate, double());
MOCK_METHOD1(RequestDartDeferredLibrary, bool(int loading_unit_id));
};
+
+class MockPlatformMessageResponse : public PlatformMessageResponse {
+ public:
+ static fml::RefPtr<MockPlatformMessageResponse> Create() {
+ return fml::AdoptRef(new MockPlatformMessageResponse());
+ }
+ MOCK_METHOD1(Complete, void(std::unique_ptr<fml::Mapping> data));
+ MOCK_METHOD0(CompleteEmpty, void());
+};
} // namespace
TEST(AndroidShellHolder, Create) {
@@ -65,5 +75,34 @@
nullptr, /*is_fake_window=*/true);
holder->GetPlatformView()->NotifyCreated(window);
}
+
+TEST(AndroidShellHolder, HandlePlatformMessage) {
+ Settings settings;
+ settings.enable_software_rendering = false;
+ auto jni = std::make_shared<MockPlatformViewAndroidJNI>();
+ auto holder = std::make_unique<AndroidShellHolder>(settings, jni);
+ EXPECT_NE(holder.get(), nullptr);
+ EXPECT_TRUE(holder->IsValid());
+ EXPECT_NE(holder->GetPlatformView().get(), nullptr);
+ auto window = fml::MakeRefCounted<AndroidNativeWindow>(
+ nullptr, /*is_fake_window=*/true);
+ holder->GetPlatformView()->NotifyCreated(window);
+ EXPECT_TRUE(holder->GetPlatformMessageHandler());
+ size_t data_size = 4;
+ fml::MallocMapping bytes =
+ fml::MallocMapping(static_cast<uint8_t*>(malloc(data_size)), data_size);
+ fml::RefPtr<MockPlatformMessageResponse> response =
+ MockPlatformMessageResponse::Create();
+ auto message = std::make_unique<PlatformMessage>(
+ /*channel=*/"foo", /*data=*/std::move(bytes), /*response=*/response);
+ int response_id = 1;
+ EXPECT_CALL(*jni,
+ FlutterViewHandlePlatformMessage(::testing::_, response_id));
+ EXPECT_CALL(*response, CompleteEmpty());
+ holder->GetPlatformMessageHandler()->HandlePlatformMessage(
+ std::move(message));
+ holder->GetPlatformMessageHandler()
+ ->InvokePlatformMessageEmptyResponseCallback(response_id);
+}
} // namespace testing
} // namespace flutter
diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
index 4ac8740..982b818 100644
--- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
+++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
@@ -41,6 +41,7 @@
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Interface between Flutter embedding's Java code and Flutter engine's C/C++ code.
@@ -98,6 +99,12 @@
@Keep
public class FlutterJNI {
private static final String TAG = "FlutterJNI";
+ // This serializes the invocation of platform message responses and the
+ // attachment and detachment of the shell holder. This ensures that we don't
+ // detach FlutterJNI on the platform thread while a background thread invokes
+ // a message response. Typically accessing the shell holder happens on the
+ // platform thread and doesn't require locking.
+ private ReentrantReadWriteLock shellHolderLock = new ReentrantReadWriteLock();
// BEGIN Methods related to loading for FlutterLoader.
/**
@@ -304,7 +311,12 @@
public void attachToNative() {
ensureRunningOnMainThread();
ensureNotAttachedToNative();
- nativeShellHolderId = performNativeAttach(this);
+ shellHolderLock.writeLock().lock();
+ try {
+ nativeShellHolderId = performNativeAttach(this);
+ } finally {
+ shellHolderLock.writeLock().unlock();
+ }
}
@VisibleForTesting
@@ -368,8 +380,13 @@
public void detachFromNativeAndReleaseResources() {
ensureRunningOnMainThread();
ensureAttachedToNative();
- nativeDestroy(nativeShellHolderId);
- nativeShellHolderId = null;
+ shellHolderLock.writeLock().lock();
+ try {
+ nativeDestroy(nativeShellHolderId);
+ nativeShellHolderId = null;
+ } finally {
+ shellHolderLock.writeLock().unlock();
+ }
}
private native void nativeDestroy(long nativeShellHolderId);
@@ -858,14 +875,33 @@
this.platformMessageHandler = platformMessageHandler;
}
- // Called by native.
+ private native void nativeCleanupMessageData(long messageData);
+
+ /**
+ * Destroys the resources provided sent to `handlePlatformMessage`.
+ *
+ * <p>This can be called on any thread.
+ *
+ * @param messageData the argument sent to handlePlatformMessage.
+ */
+ public void cleanupMessageData(long messageData) {
+ // This doesn't rely on being attached like other methods.
+ nativeCleanupMessageData(messageData);
+ }
+
+ // Called by native on the ui thread.
// TODO(mattcarroll): determine if message is nonull or nullable
@SuppressWarnings("unused")
@VisibleForTesting
public void handlePlatformMessage(
- @NonNull final String channel, ByteBuffer message, final int replyId) {
+ @NonNull final String channel,
+ ByteBuffer message,
+ final int replyId,
+ final long messageData) {
if (platformMessageHandler != null) {
- platformMessageHandler.handleMessageFromDart(channel, message, replyId);
+ platformMessageHandler.handleMessageFromDart(channel, message, replyId, messageData);
+ } else {
+ nativeCleanupMessageData(messageData);
}
// TODO(mattcarroll): log dropped messages when in debug mode
// (https://github.com/flutter/flutter/issues/25391)
@@ -931,16 +967,20 @@
int responseId);
// TODO(mattcarroll): differentiate between channel responses and platform responses.
- @UiThread
public void invokePlatformMessageEmptyResponseCallback(int responseId) {
- ensureRunningOnMainThread();
- if (isAttached()) {
- nativeInvokePlatformMessageEmptyResponseCallback(nativeShellHolderId, responseId);
- } else {
- Log.w(
- TAG,
- "Tried to send a platform message response, but FlutterJNI was detached from native C++. Could not send. Response ID: "
- + responseId);
+ // Called on any thread.
+ shellHolderLock.readLock().lock();
+ try {
+ if (isAttached()) {
+ nativeInvokePlatformMessageEmptyResponseCallback(nativeShellHolderId, responseId);
+ } else {
+ Log.w(
+ TAG,
+ "Tried to send a platform message response, but FlutterJNI was detached from native C++. Could not send. Response ID: "
+ + responseId);
+ }
+ } finally {
+ shellHolderLock.readLock().unlock();
}
}
@@ -949,21 +989,25 @@
long nativeShellHolderId, int responseId);
// TODO(mattcarroll): differentiate between channel responses and platform responses.
- @UiThread
public void invokePlatformMessageResponseCallback(
int responseId, @NonNull ByteBuffer message, int position) {
- ensureRunningOnMainThread();
+ // Called on any thread.
if (!message.isDirect()) {
throw new IllegalArgumentException("Expected a direct ByteBuffer.");
}
- if (isAttached()) {
- nativeInvokePlatformMessageResponseCallback(
- nativeShellHolderId, responseId, message, position);
- } else {
- Log.w(
- TAG,
- "Tried to send a platform message response, but FlutterJNI was detached from native C++. Could not send. Response ID: "
- + responseId);
+ shellHolderLock.readLock().lock();
+ try {
+ if (isAttached()) {
+ nativeInvokePlatformMessageResponseCallback(
+ nativeShellHolderId, responseId, message, position);
+ } else {
+ Log.w(
+ TAG,
+ "Tried to send a platform message response, but FlutterJNI was detached from native C++. Could not send. Response ID: "
+ + responseId);
+ }
+ } finally {
+ shellHolderLock.readLock().unlock();
}
}
diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java
index 5118881..dc5de3d 100644
--- a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java
+++ b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java
@@ -59,7 +59,7 @@
this.flutterJNI = flutterJNI;
this.assetManager = assetManager;
this.dartMessenger = new DartMessenger(flutterJNI);
- dartMessenger.setMessageHandler("flutter/isolate", isolateChannelMessageHandler);
+ dartMessenger.setMessageHandler("flutter/isolate", isolateChannelMessageHandler, null);
this.binaryMessenger = new DefaultBinaryMessenger(dartMessenger);
// The JNI might already be attached if coming from a spawned engine. If so, correctly report
// that this DartExecutor is already running.
@@ -170,6 +170,14 @@
// ------ START BinaryMessenger (Deprecated: use getBinaryMessenger() instead) -----
/** @deprecated Use {@link #getBinaryMessenger()} instead. */
@Deprecated
+ @UiThread
+ @Override
+ public TaskQueue makeBackgroundTaskQueue() {
+ return binaryMessenger.makeBackgroundTaskQueue();
+ }
+
+ /** @deprecated Use {@link #getBinaryMessenger()} instead. */
+ @Deprecated
@Override
@UiThread
public void send(@NonNull String channel, @Nullable ByteBuffer message) {
@@ -192,8 +200,10 @@
@Override
@UiThread
public void setMessageHandler(
- @NonNull String channel, @Nullable BinaryMessenger.BinaryMessageHandler handler) {
- binaryMessenger.setMessageHandler(channel, handler);
+ @NonNull String channel,
+ @Nullable BinaryMessenger.BinaryMessageHandler handler,
+ @Nullable TaskQueue taskQueue) {
+ binaryMessenger.setMessageHandler(channel, handler, taskQueue);
}
// ------ END BinaryMessenger -----
@@ -371,6 +381,10 @@
this.messenger = messenger;
}
+ public TaskQueue makeBackgroundTaskQueue() {
+ return messenger.makeBackgroundTaskQueue();
+ }
+
/**
* Sends the given {@code message} from Android to Dart over the given {@code channel}.
*
@@ -413,8 +427,10 @@
@Override
@UiThread
public void setMessageHandler(
- @NonNull String channel, @Nullable BinaryMessenger.BinaryMessageHandler handler) {
- messenger.setMessageHandler(channel, handler);
+ @NonNull String channel,
+ @Nullable BinaryMessenger.BinaryMessageHandler handler,
+ @Nullable TaskQueue taskQueue) {
+ messenger.setMessageHandler(channel, handler, taskQueue);
}
}
}
diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java b/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java
index 2822b40..7e6b970 100644
--- a/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java
+++ b/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java
@@ -13,6 +13,11 @@
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;
/**
@@ -26,25 +31,104 @@
private static final String TAG = "DartMessenger";
@NonNull private final FlutterJNI flutterJNI;
- @NonNull private final Map<String, BinaryMessenger.BinaryMessageHandler> messageHandlers;
+
+ @NonNull private final ConcurrentHashMap<String, HandlerInfo> messageHandlers;
+
@NonNull private final Map<Integer, BinaryMessenger.BinaryReply> pendingReplies;
private int nextReplyId = 1;
- DartMessenger(@NonNull FlutterJNI flutterJNI) {
+ @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 HashMap<>();
+ 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) {
+ @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, handler);
+ messageHandlers.put(channel, new HandlerInfo(handler, dartMessengerTaskQueue));
}
}
@@ -72,20 +156,13 @@
}
}
- @Override
- public void handleMessageFromDart(
- @NonNull final String channel, @Nullable ByteBuffer message, final int replyId) {
- Log.v(TAG, "Received message from Dart over channel '" + channel + "'");
- BinaryMessenger.BinaryMessageHandler handler = messageHandlers.get(channel);
- if (handler != null) {
+ 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.");
- handler.onMessage(message, new Reply(flutterJNI, 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);
- }
+ handlerInfo.handler.onMessage(message, new Reply(flutterJNI, replyId));
} catch (Exception ex) {
Log.e(TAG, "Uncaught exception in binary message listener", ex);
flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
@@ -99,6 +176,37 @@
}
@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);
diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java b/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java
index 22bb6f1..36a7a9d 100644
--- a/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java
+++ b/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java
@@ -11,7 +11,10 @@
/** Handler that receives messages from Dart code. */
public interface PlatformMessageHandler {
void handleMessageFromDart(
- @NonNull final String channel, @Nullable ByteBuffer message, final int replyId);
+ @NonNull final String channel,
+ @Nullable ByteBuffer message,
+ final int replyId,
+ long messageData);
void handlePlatformMessageResponse(int replyId, @Nullable ByteBuffer reply);
}
diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/PlatformTaskQueue.java b/shell/platform/android/io/flutter/embedding/engine/dart/PlatformTaskQueue.java
new file mode 100644
index 0000000..e90ab89
--- /dev/null
+++ b/shell/platform/android/io/flutter/embedding/engine/dart/PlatformTaskQueue.java
@@ -0,0 +1,19 @@
+// 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 android.os.Handler;
+import android.os.Looper;
+import androidx.annotation.NonNull;
+
+/** A BinaryMessenger.TaskQueue that posts to the platform thread (aka main thread). */
+public class PlatformTaskQueue implements DartMessenger.DartMessengerTaskQueue {
+ @NonNull private final Handler handler = new Handler(Looper.getMainLooper());
+
+ @Override
+ public void dispatch(@NonNull Runnable runnable) {
+ handler.post(runnable);
+ }
+}
diff --git a/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java b/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java
index 55eb310..fa9ffe2 100644
--- a/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java
+++ b/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java
@@ -36,6 +36,7 @@
@NonNull private final BinaryMessenger messenger;
@NonNull private final String name;
@NonNull private final MessageCodec<T> codec;
+ @Nullable private final BinaryMessenger.TaskQueue taskQueue;
/**
* Creates a new channel associated with the specified {@link BinaryMessenger} and with the
@@ -47,6 +48,25 @@
*/
public BasicMessageChannel(
@NonNull BinaryMessenger messenger, @NonNull String name, @NonNull MessageCodec<T> codec) {
+ this(messenger, name, codec, null);
+ }
+
+ /**
+ * Creates a new channel associated with the specified {@link BinaryMessenger} and with the
+ * specified name and {@link MessageCodec}.
+ *
+ * @param messenger a {@link BinaryMessenger}.
+ * @param name a channel name String.
+ * @param codec a {@link MessageCodec}.
+ * @param taskQueue a {@link BinaryMessenger.TaskQueue} that specifies what thread will execute
+ * the handler. Specifying null means execute on the platform thread. See also {@link
+ * BinaryMessenger#makeBackgroundTaskQueue()}.
+ */
+ public BasicMessageChannel(
+ @NonNull BinaryMessenger messenger,
+ @NonNull String name,
+ @NonNull MessageCodec<T> codec,
+ BinaryMessenger.TaskQueue taskQueue) {
if (BuildConfig.DEBUG) {
if (messenger == null) {
Log.e(TAG, "Parameter messenger must not be null.");
@@ -61,6 +81,7 @@
this.messenger = messenger;
this.name = name;
this.codec = codec;
+ this.taskQueue = taskQueue;
}
/**
@@ -101,7 +122,8 @@
*/
@UiThread
public void setMessageHandler(@Nullable final MessageHandler<T> handler) {
- messenger.setMessageHandler(name, handler == null ? null : new IncomingMessageHandler(handler));
+ messenger.setMessageHandler(
+ name, handler == null ? null : new IncomingMessageHandler(handler), taskQueue);
}
/**
diff --git a/shell/platform/android/io/flutter/plugin/common/BinaryMessenger.java b/shell/platform/android/io/flutter/plugin/common/BinaryMessenger.java
index fb1a22e..433a6a9 100644
--- a/shell/platform/android/io/flutter/plugin/common/BinaryMessenger.java
+++ b/shell/platform/android/io/flutter/plugin/common/BinaryMessenger.java
@@ -27,6 +27,24 @@
*/
public interface BinaryMessenger {
/**
+ * An abstraction over the threading policy used to invoke message handlers.
+ *
+ * <p>These are generated by calling methods like {@link
+ * BinaryMessenger#makeBackgroundTaskQueue()} and can be passed into platform channels'
+ * constructors to control the threading policy for handling platform channels' messages.
+ */
+ public interface TaskQueue {}
+
+ /**
+ * Creates a TaskQueue that executes the tasks serially on a background thread.
+ *
+ * <p>There is no guarantee that the tasks will execute on the same thread, just that execution is
+ * serial.
+ */
+ @UiThread
+ TaskQueue makeBackgroundTaskQueue();
+
+ /**
* Sends a binary message to the Flutter application.
*
* @param channel the name {@link String} of the logical channel used for the message.
@@ -62,9 +80,14 @@
*
* @param channel the name {@link String} of the channel.
* @param handler a {@link BinaryMessageHandler} to be invoked on incoming messages, or null.
+ * @param taskQueue a {@link BinaryMessenger.TaskQueue} that specifies what thread will execute
+ * the handler. Specifying null means execute on the platform thread.
*/
@UiThread
- void setMessageHandler(@NonNull String channel, @Nullable BinaryMessageHandler handler);
+ void setMessageHandler(
+ @NonNull String channel,
+ @Nullable BinaryMessageHandler handler,
+ @Nullable TaskQueue taskQueue);
/** Handler for incoming binary messages from Flutter. */
interface BinaryMessageHandler {
@@ -97,7 +120,6 @@
* outgoing replies must place the reply bytes between position zero and current position.
* Reply receivers can read from the buffer directly.
*/
- @UiThread
void reply(@Nullable ByteBuffer reply);
}
}
diff --git a/shell/platform/android/io/flutter/plugin/common/EventChannel.java b/shell/platform/android/io/flutter/plugin/common/EventChannel.java
index 984b251..70bd7d5 100644
--- a/shell/platform/android/io/flutter/plugin/common/EventChannel.java
+++ b/shell/platform/android/io/flutter/plugin/common/EventChannel.java
@@ -4,6 +4,7 @@
package io.flutter.plugin.common;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import io.flutter.BuildConfig;
import io.flutter.Log;
@@ -34,6 +35,7 @@
private final BinaryMessenger messenger;
private final String name;
private final MethodCodec codec;
+ @Nullable private final BinaryMessenger.TaskQueue taskQueue;
/**
* Creates a new channel associated with the specified {@link BinaryMessenger} and with the
@@ -55,6 +57,25 @@
* @param codec a {@link MessageCodec}.
*/
public EventChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
+ this(messenger, name, codec, null);
+ }
+
+ /**
+ * Creates a new channel associated with the specified {@link BinaryMessenger} and with the
+ * specified name and {@link MethodCodec}.
+ *
+ * @param messenger a {@link BinaryMessenger}.
+ * @param name a channel name String.
+ * @param codec a {@link MessageCodec}.
+ * @param taskQueue a {@link BinaryMessenger.TaskQueue} that specifies what thread will execute
+ * the handler. Specifying null means execute on the platform thread. See also {@link
+ * BinaryMessenger#makeBackgroundTaskQueue()}.
+ */
+ public EventChannel(
+ BinaryMessenger messenger,
+ String name,
+ MethodCodec codec,
+ BinaryMessenger.TaskQueue taskQueue) {
if (BuildConfig.DEBUG) {
if (messenger == null) {
Log.e(TAG, "Parameter messenger must not be null.");
@@ -69,6 +90,7 @@
this.messenger = messenger;
this.name = name;
this.codec = codec;
+ this.taskQueue = taskQueue;
}
/**
@@ -84,7 +106,7 @@
@UiThread
public void setStreamHandler(final StreamHandler handler) {
messenger.setMessageHandler(
- name, handler == null ? null : new IncomingStreamRequestHandler(handler));
+ name, handler == null ? null : new IncomingStreamRequestHandler(handler), taskQueue);
}
/**
diff --git a/shell/platform/android/io/flutter/plugin/common/MethodChannel.java b/shell/platform/android/io/flutter/plugin/common/MethodChannel.java
index 901299b..66c92cf 100644
--- a/shell/platform/android/io/flutter/plugin/common/MethodChannel.java
+++ b/shell/platform/android/io/flutter/plugin/common/MethodChannel.java
@@ -35,6 +35,7 @@
private final BinaryMessenger messenger;
private final String name;
private final MethodCodec codec;
+ private final BinaryMessenger.TaskQueue taskQueue;
/**
* Creates a new channel associated with the specified {@link BinaryMessenger} and with the
@@ -56,6 +57,25 @@
* @param codec a {@link MessageCodec}.
*/
public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
+ this(messenger, name, codec, null);
+ }
+
+ /**
+ * Creates a new channel associated with the specified {@link BinaryMessenger} and with the
+ * specified name and {@link MethodCodec}.
+ *
+ * @param messenger a {@link BinaryMessenger}.
+ * @param name a channel name String.
+ * @param codec a {@link MessageCodec}.
+ * @param taskQueue a {@link BinaryMessenger.TaskQueue} that specifies what thread will execute
+ * the handler. Specifying null means execute on the platform thread. See also {@link
+ * BinaryMessenger#makeBackgroundTaskQueue()}.
+ */
+ public MethodChannel(
+ BinaryMessenger messenger,
+ String name,
+ MethodCodec codec,
+ @Nullable BinaryMessenger.TaskQueue taskQueue) {
if (BuildConfig.DEBUG) {
if (messenger == null) {
Log.e(TAG, "Parameter messenger must not be null.");
@@ -70,6 +90,7 @@
this.messenger = messenger;
this.name = name;
this.codec = codec;
+ this.taskQueue = taskQueue;
}
/**
@@ -117,7 +138,7 @@
@UiThread
public void setMethodCallHandler(final @Nullable MethodCallHandler handler) {
messenger.setMessageHandler(
- name, handler == null ? null : new IncomingMethodCallHandler(handler));
+ name, handler == null ? null : new IncomingMethodCallHandler(handler), taskQueue);
}
/**
diff --git a/shell/platform/android/io/flutter/view/FlutterNativeView.java b/shell/platform/android/io/flutter/view/FlutterNativeView.java
index 63005dc..6e6ca76 100644
--- a/shell/platform/android/io/flutter/view/FlutterNativeView.java
+++ b/shell/platform/android/io/flutter/view/FlutterNativeView.java
@@ -126,6 +126,12 @@
@Override
@UiThread
+ public TaskQueue makeBackgroundTaskQueue() {
+ return dartExecutor.getBinaryMessenger().makeBackgroundTaskQueue();
+ }
+
+ @Override
+ @UiThread
public void send(String channel, ByteBuffer message) {
dartExecutor.getBinaryMessenger().send(channel, message);
}
@@ -143,8 +149,8 @@
@Override
@UiThread
- public void setMessageHandler(String channel, BinaryMessageHandler handler) {
- dartExecutor.getBinaryMessenger().setMessageHandler(channel, handler);
+ public void setMessageHandler(String channel, BinaryMessageHandler handler, TaskQueue taskQueue) {
+ dartExecutor.getBinaryMessenger().setMessageHandler(channel, handler, taskQueue);
}
/*package*/ FlutterJNI getFlutterJNI() {
diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java
index 4876f1b..f63ce7a 100644
--- a/shell/platform/android/io/flutter/view/FlutterView.java
+++ b/shell/platform/android/io/flutter/view/FlutterView.java
@@ -824,6 +824,12 @@
@Override
@UiThread
+ public TaskQueue makeBackgroundTaskQueue() {
+ return null;
+ }
+
+ @Override
+ @UiThread
public void send(String channel, ByteBuffer message) {
send(channel, message, null);
}
@@ -840,8 +846,8 @@
@Override
@UiThread
- public void setMessageHandler(String channel, BinaryMessageHandler handler) {
- mNativeView.setMessageHandler(channel, handler);
+ public void setMessageHandler(String channel, BinaryMessageHandler handler, TaskQueue taskQueue) {
+ mNativeView.setMessageHandler(channel, handler, taskQueue);
}
/** Listener will be called on the Android UI thread once when Flutter renders the first frame. */
diff --git a/shell/platform/android/platform_message_handler_android.cc b/shell/platform/android/platform_message_handler_android.cc
new file mode 100644
index 0000000..2179611
--- /dev/null
+++ b/shell/platform/android/platform_message_handler_android.cc
@@ -0,0 +1,64 @@
+
+#include "flutter/shell/platform/android/platform_message_handler_android.h"
+
+namespace flutter {
+
+PlatformMessageHandlerAndroid::PlatformMessageHandlerAndroid(
+ const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade)
+ : jni_facade_(jni_facade) {}
+
+void PlatformMessageHandlerAndroid::InvokePlatformMessageResponseCallback(
+ int response_id,
+ std::unique_ptr<fml::Mapping> mapping) {
+ // Called from any thread.
+ if (!response_id) {
+ return;
+ }
+ // TODO(gaaclarke): Move the jump to the ui thread here from
+ // PlatformMessageResponseDart so we won't need to use a mutex anymore.
+ fml::RefPtr<flutter::PlatformMessageResponse> message_response;
+ {
+ std::lock_guard lock(pending_responses_mutex_);
+ auto it = pending_responses_.find(response_id);
+ if (it == pending_responses_.end())
+ return;
+ message_response = std::move(it->second);
+ pending_responses_.erase(it);
+ }
+
+ message_response->Complete(std::move(mapping));
+}
+
+void PlatformMessageHandlerAndroid::InvokePlatformMessageEmptyResponseCallback(
+ int response_id) {
+ // Called from any thread.
+ if (!response_id) {
+ return;
+ }
+ fml::RefPtr<flutter::PlatformMessageResponse> message_response;
+ {
+ std::lock_guard lock(pending_responses_mutex_);
+ auto it = pending_responses_.find(response_id);
+ if (it == pending_responses_.end())
+ return;
+ message_response = std::move(it->second);
+ pending_responses_.erase(it);
+ }
+ message_response->CompleteEmpty();
+}
+
+// |PlatformView|
+void PlatformMessageHandlerAndroid::HandlePlatformMessage(
+ std::unique_ptr<flutter::PlatformMessage> message) {
+ // Called from the ui thread.
+ int response_id = next_response_id_++;
+ if (auto response = message->response()) {
+ std::lock_guard lock(pending_responses_mutex_);
+ pending_responses_[response_id] = response;
+ }
+ // This call can re-enter in InvokePlatformMessageXxxResponseCallback.
+ jni_facade_->FlutterViewHandlePlatformMessage(std::move(message),
+ response_id);
+}
+
+} // namespace flutter
diff --git a/shell/platform/android/platform_message_handler_android.h b/shell/platform/android/platform_message_handler_android.h
new file mode 100644
index 0000000..dc36013
--- /dev/null
+++ b/shell/platform/android/platform_message_handler_android.h
@@ -0,0 +1,38 @@
+// 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.
+
+#ifndef SHELL_PLATFORM_ANDROID_PLATFORM_MESSAGE_HANDLER_H_
+#define SHELL_PLATFORM_ANDROID_PLATFORM_MESSAGE_HANDLER_H_
+
+#include <jni.h>
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+
+#include "flutter/lib/ui/window/platform_message.h"
+#include "flutter/shell/common/platform_message_handler.h"
+#include "flutter/shell/platform/android/jni/platform_view_android_jni.h"
+
+namespace flutter {
+class PlatformMessageHandlerAndroid : public PlatformMessageHandler {
+ public:
+ PlatformMessageHandlerAndroid(
+ const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade);
+ void HandlePlatformMessage(std::unique_ptr<PlatformMessage> message) override;
+ void InvokePlatformMessageResponseCallback(
+ int response_id,
+ std::unique_ptr<fml::Mapping> mapping) override;
+
+ void InvokePlatformMessageEmptyResponseCallback(int response_id) override;
+
+ private:
+ const std::shared_ptr<PlatformViewAndroidJNI> jni_facade_;
+ int next_response_id_ = 1;
+ std::unordered_map<int, fml::RefPtr<flutter::PlatformMessageResponse>>
+ pending_responses_;
+ std::mutex pending_responses_mutex_;
+};
+} // namespace flutter
+
+#endif
diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc
index 50b9d55..064bbf9 100644
--- a/shell/platform/android/platform_view_android.cc
+++ b/shell/platform/android/platform_view_android.cc
@@ -73,7 +73,8 @@
: PlatformView(delegate, std::move(task_runners)),
jni_facade_(jni_facade),
android_context_(std::move(android_context)),
- platform_view_android_delegate_(jni_facade) {
+ platform_view_android_delegate_(jni_facade),
+ platform_message_handler_(new PlatformMessageHandlerAndroid(jni_facade)) {
// TODO(dnfield): always create a pbuffer surface for background use to
// resolve https://github.com/flutter/flutter/issues/73675
if (android_context_) {
@@ -190,51 +191,11 @@
std::move(response)));
}
-void PlatformViewAndroid::InvokePlatformMessageResponseCallback(
- JNIEnv* env,
- jint response_id,
- jobject java_response_data,
- jint java_response_position) {
- if (!response_id)
- return;
- auto it = pending_responses_.find(response_id);
- if (it == pending_responses_.end())
- return;
- uint8_t* response_data =
- static_cast<uint8_t*>(env->GetDirectBufferAddress(java_response_data));
- FML_DCHECK(response_data != nullptr);
- std::vector<uint8_t> response = std::vector<uint8_t>(
- response_data, response_data + java_response_position);
- auto message_response = std::move(it->second);
- pending_responses_.erase(it);
- message_response->Complete(
- std::make_unique<fml::DataMapping>(std::move(response)));
-}
-
-void PlatformViewAndroid::InvokePlatformMessageEmptyResponseCallback(
- JNIEnv* env,
- jint response_id) {
- if (!response_id)
- return;
- auto it = pending_responses_.find(response_id);
- if (it == pending_responses_.end())
- return;
- auto message_response = std::move(it->second);
- pending_responses_.erase(it);
- message_response->CompleteEmpty();
-}
-
// |PlatformView|
void PlatformViewAndroid::HandlePlatformMessage(
std::unique_ptr<flutter::PlatformMessage> message) {
- int response_id = next_response_id_++;
- if (auto response = message->response()) {
- pending_responses_[response_id] = response;
- }
- // This call can re-enter in InvokePlatformMessageXxxResponseCallback.
- jni_facade_->FlutterViewHandlePlatformMessage(std::move(message),
- response_id);
- message = nullptr;
+ // Called from the ui thread.
+ platform_message_handler_->HandlePlatformMessage(std::move(message));
}
// |PlatformView|
diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h
index 1bd959e..0ff654f 100644
--- a/shell/platform/android/platform_view_android.h
+++ b/shell/platform/android/platform_view_android.h
@@ -17,6 +17,7 @@
#include "flutter/shell/common/snapshot_surface_producer.h"
#include "flutter/shell/platform/android/context/android_context.h"
#include "flutter/shell/platform/android/jni/platform_view_android_jni.h"
+#include "flutter/shell/platform/android/platform_message_handler_android.h"
#include "flutter/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.h"
#include "flutter/shell/platform/android/surface/android_native_window.h"
#include "flutter/shell/platform/android/surface/android_surface.h"
@@ -79,14 +80,6 @@
std::string name,
jint response_id);
- void InvokePlatformMessageResponseCallback(JNIEnv* env,
- jint response_id,
- jobject java_response_data,
- jint java_response_position);
-
- void InvokePlatformMessageEmptyResponseCallback(JNIEnv* env,
- jint response_id);
-
void DispatchSemanticsAction(JNIEnv* env,
jint id,
jint action,
@@ -116,6 +109,11 @@
return android_context_;
}
+ std::shared_ptr<PlatformMessageHandler> GetPlatformMessageHandler()
+ const override {
+ return platform_message_handler_;
+ }
+
private:
const std::shared_ptr<PlatformViewAndroidJNI> jni_facade_;
std::shared_ptr<AndroidContext> android_context_;
@@ -124,11 +122,7 @@
PlatformViewAndroidDelegate platform_view_android_delegate_;
std::unique_ptr<AndroidSurface> android_surface_;
-
- // We use id 0 to mean that no response is expected.
- int next_response_id_ = 1;
- std::unordered_map<int, fml::RefPtr<flutter::PlatformMessageResponse>>
- pending_responses_;
+ std::shared_ptr<PlatformMessageHandlerAndroid> platform_message_handler_;
// |PlatformView|
void UpdateSemantics(
diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc
index 376ac15..8b8494b 100644
--- a/shell/platform/android/platform_view_android_jni_impl.cc
+++ b/shell/platform/android/platform_view_android_jni_impl.cc
@@ -405,6 +405,13 @@
);
}
+static void CleanupMessageData(JNIEnv* env,
+ jobject jcaller,
+ jlong message_data) {
+ // Called from any thread.
+ free(reinterpret_cast<void*>(message_data));
+}
+
static void DispatchPointerDataPacket(JNIEnv* env,
jobject jcaller,
jlong shell_holder,
@@ -483,22 +490,21 @@
jint responseId,
jobject message,
jint position) {
- ANDROID_SHELL_HOLDER->GetPlatformView()
- ->InvokePlatformMessageResponseCallback(env, //
- responseId, //
- message, //
- position //
- );
+ uint8_t* response_data =
+ static_cast<uint8_t*>(env->GetDirectBufferAddress(message));
+ FML_DCHECK(response_data != nullptr);
+ auto mapping = std::make_unique<fml::MallocMapping>(
+ fml::MallocMapping::Copy(response_data, response_data + position));
+ ANDROID_SHELL_HOLDER->GetPlatformMessageHandler()
+ ->InvokePlatformMessageResponseCallback(responseId, std::move(mapping));
}
static void InvokePlatformMessageEmptyResponseCallback(JNIEnv* env,
jobject jcaller,
jlong shell_holder,
jint responseId) {
- ANDROID_SHELL_HOLDER->GetPlatformView()
- ->InvokePlatformMessageEmptyResponseCallback(env, //
- responseId //
- );
+ ANDROID_SHELL_HOLDER->GetPlatformMessageHandler()
+ ->InvokePlatformMessageEmptyResponseCallback(responseId);
}
static void NotifyLowMemoryWarning(JNIEnv* env,
@@ -639,6 +645,11 @@
.fnPtr = reinterpret_cast<void*>(&DispatchEmptyPlatformMessage),
},
{
+ .name = "nativeCleanupMessageData",
+ .signature = "(J)V",
+ .fnPtr = reinterpret_cast<void*>(&CleanupMessageData),
+ },
+ {
.name = "nativeDispatchPlatformMessage",
.signature = "(JLjava/lang/String;Ljava/nio/ByteBuffer;II)V",
.fnPtr = reinterpret_cast<void*>(&DispatchPlatformMessage),
@@ -819,7 +830,7 @@
g_handle_platform_message_method =
env->GetMethodID(g_flutter_jni_class->obj(), "handlePlatformMessage",
- "(Ljava/lang/String;Ljava/nio/ByteBuffer;I)V");
+ "(Ljava/lang/String;Ljava/nio/ByteBuffer;IJ)V");
if (g_handle_platform_message_method == nullptr) {
FML_LOG(ERROR) << "Could not locate handlePlatformMessage method";
@@ -1102,6 +1113,7 @@
void PlatformViewAndroidJNIImpl::FlutterViewHandlePlatformMessage(
std::unique_ptr<flutter::PlatformMessage> message,
int responseId) {
+ // Called from the ui thread.
JNIEnv* env = fml::jni::AttachCurrentThread();
auto java_object = java_object_.get(env);
@@ -1117,11 +1129,14 @@
env, env->NewDirectByteBuffer(
const_cast<uint8_t*>(message->data().GetMapping()),
message->data().GetSize()));
+ // Message data is deleted in CleanupMessageData.
+ fml::MallocMapping mapping = message->releaseData();
env->CallVoidMethod(java_object.obj(), g_handle_platform_message_method,
- java_channel.obj(), message_array.obj(), responseId);
+ java_channel.obj(), message_array.obj(), responseId,
+ mapping.Release());
} else {
env->CallVoidMethod(java_object.obj(), g_handle_platform_message_method,
- java_channel.obj(), nullptr, responseId);
+ java_channel.obj(), nullptr, responseId, nullptr);
}
FML_CHECK(fml::jni::CheckException(env));
diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java
index 5e19221..4e7a6fd 100644
--- a/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java
@@ -10,6 +10,7 @@
import static org.mockito.Mockito.verify;
import io.flutter.embedding.engine.FlutterJNI;
+import io.flutter.embedding.engine.dart.DartMessenger.DartMessengerTaskQueue;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler;
import java.nio.ByteBuffer;
@@ -22,6 +23,8 @@
@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class DartMessengerTest {
+ SynchronousTaskQueue synchronousTaskQueue = new SynchronousTaskQueue();
+
private static class ReportingUncaughtExceptionHandler
implements Thread.UncaughtExceptionHandler {
public Throwable latestException;
@@ -32,6 +35,12 @@
}
}
+ private static class SynchronousTaskQueue implements DartMessengerTaskQueue {
+ public void dispatch(Runnable runnable) {
+ runnable.run();
+ }
+ }
+
@Test
public void itHandlesErrors() {
// Setup test.
@@ -44,14 +53,14 @@
currentThread.setUncaughtExceptionHandler(reportingHandler);
// Create object under test.
- final DartMessenger messenger = new DartMessenger(fakeFlutterJni);
+ final DartMessenger messenger = new DartMessenger(fakeFlutterJni, () -> synchronousTaskQueue);
final BinaryMessageHandler throwingHandler = mock(BinaryMessageHandler.class);
Mockito.doThrow(AssertionError.class)
.when(throwingHandler)
.onMessage(any(ByteBuffer.class), any(DartMessenger.Reply.class));
-
- messenger.setMessageHandler("test", throwingHandler);
- messenger.handleMessageFromDart("test", ByteBuffer.allocate(0), 0);
+ BinaryMessenger.TaskQueue taskQueue = messenger.makeBackgroundTaskQueue();
+ messenger.setMessageHandler("test", throwingHandler, taskQueue);
+ messenger.handleMessageFromDart("test", ByteBuffer.allocate(0), 0, 0);
assertNotNull(reportingHandler.latestException);
assertTrue(reportingHandler.latestException instanceof AssertionError);
currentThread.setUncaughtExceptionHandler(savedHandler);
@@ -61,21 +70,22 @@
public void givesDirectByteBuffer() {
// Setup test.
final FlutterJNI fakeFlutterJni = mock(FlutterJNI.class);
- final DartMessenger messenger = new DartMessenger(fakeFlutterJni);
+ final DartMessenger messenger = new DartMessenger(fakeFlutterJni, () -> synchronousTaskQueue);
final String channel = "foobar";
final boolean[] wasDirect = {false};
final BinaryMessenger.BinaryMessageHandler handler =
(message, reply) -> {
wasDirect[0] = message.isDirect();
};
- messenger.setMessageHandler(channel, handler);
+ BinaryMessenger.TaskQueue taskQueue = messenger.makeBackgroundTaskQueue();
+ messenger.setMessageHandler(channel, handler, taskQueue);
final ByteBuffer message = ByteBuffer.allocateDirect(4 * 2);
message.rewind();
message.putChar('a');
message.putChar('b');
message.putChar('c');
message.putChar('d');
- messenger.handleMessageFromDart(channel, message, /*replyId=*/ 123);
+ messenger.handleMessageFromDart(channel, message, /*replyId=*/ 123, 0);
assertTrue(wasDirect[0]);
}
@@ -83,7 +93,7 @@
public void directByteBufferLimitZeroAfterUsage() {
// Setup test.
final FlutterJNI fakeFlutterJni = mock(FlutterJNI.class);
- final DartMessenger messenger = new DartMessenger(fakeFlutterJni);
+ final DartMessenger messenger = new DartMessenger(fakeFlutterJni, () -> synchronousTaskQueue);
final String channel = "foobar";
final ByteBuffer[] byteBuffers = {null};
final int bufferSize = 4 * 2;
@@ -92,14 +102,15 @@
byteBuffers[0] = message;
assertEquals(bufferSize, byteBuffers[0].limit());
};
- messenger.setMessageHandler(channel, handler);
+ BinaryMessenger.TaskQueue taskQueue = messenger.makeBackgroundTaskQueue();
+ messenger.setMessageHandler(channel, handler, taskQueue);
final ByteBuffer message = ByteBuffer.allocateDirect(bufferSize);
message.rewind();
message.putChar('a');
message.putChar('b');
message.putChar('c');
message.putChar('d');
- messenger.handleMessageFromDart(channel, message, /*replyId=*/ 123);
+ messenger.handleMessageFromDart(channel, message, /*replyId=*/ 123, 0);
assertNotNull(byteBuffers[0]);
assertTrue(byteBuffers[0].isDirect());
assertEquals(0, byteBuffers[0].limit());
@@ -139,4 +150,40 @@
messenger.send(channel, null, null);
verify(fakeFlutterJni, times(1)).dispatchEmptyPlatformMessage(eq("foobar"), eq(2));
}
+
+ @Test
+ public void cleansUpMessageData() throws InterruptedException {
+ final FlutterJNI fakeFlutterJni = mock(FlutterJNI.class);
+ final DartMessenger messenger = new DartMessenger(fakeFlutterJni, () -> synchronousTaskQueue);
+ BinaryMessenger.TaskQueue taskQueue = messenger.makeBackgroundTaskQueue();
+ String channel = "foobar";
+ BinaryMessenger.BinaryMessageHandler handler =
+ (ByteBuffer message, BinaryMessenger.BinaryReply reply) -> {
+ reply.reply(null);
+ };
+ messenger.setMessageHandler(channel, handler, taskQueue);
+ final ByteBuffer message = ByteBuffer.allocateDirect(4 * 2);
+ int replyId = 1;
+ long messageData = 1234;
+ messenger.handleMessageFromDart(channel, message, replyId, messageData);
+ verify(fakeFlutterJni).cleanupMessageData(eq(messageData));
+ }
+
+ @Test
+ public void cleansUpMessageDataOnError() throws InterruptedException {
+ final FlutterJNI fakeFlutterJni = mock(FlutterJNI.class);
+ final DartMessenger messenger = new DartMessenger(fakeFlutterJni, () -> synchronousTaskQueue);
+ BinaryMessenger.TaskQueue taskQueue = messenger.makeBackgroundTaskQueue();
+ String channel = "foobar";
+ BinaryMessenger.BinaryMessageHandler handler =
+ (ByteBuffer message, BinaryMessenger.BinaryReply reply) -> {
+ throw new RuntimeException("hello");
+ };
+ messenger.setMessageHandler(channel, handler, taskQueue);
+ final ByteBuffer message = ByteBuffer.allocateDirect(4 * 2);
+ int replyId = 1;
+ long messageData = 1234;
+ messenger.handleMessageFromDart(channel, message, replyId, messageData);
+ verify(fakeFlutterJni).cleanupMessageData(eq(messageData));
+ }
}
diff --git a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java
index 0c85834..c937721 100644
--- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java
+++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java
@@ -1884,7 +1884,7 @@
textInputChannel.setTextInputMethodHandler(mockHandler);
verify(mockBinaryMessenger, times(1))
- .setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
+ .setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture(), eq(null));
BinaryMessenger.BinaryMessageHandler binaryMessageHandler =
binaryMessageHandlerCaptor.getValue();
@@ -1917,7 +1917,7 @@
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
verify(mockBinaryMessenger, times(1))
- .setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
+ .setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture(), eq(null));
JSONObject arguments = new JSONObject();
arguments.put("action", "actionCommand");
@@ -1948,7 +1948,7 @@
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
verify(mockBinaryMessenger, times(1))
- .setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
+ .setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture(), eq(null));
JSONObject arguments = new JSONObject();
arguments.put("action", "actionCommand");
diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java
index 21c3928..38c919c 100644
--- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java
+++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java
@@ -218,7 +218,7 @@
}
@Test
- @Config(shadows = {ShadowFlutterJNI.class})
+ @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void getPlatformViewById__hybridComposition() {
PlatformViewsController platformViewsController = new PlatformViewsController();
@@ -246,7 +246,7 @@
}
@Test
- @Config(shadows = {ShadowFlutterJNI.class})
+ @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void createPlatformViewMessage__initializesAndroidView() {
PlatformViewsController platformViewsController = new PlatformViewsController();
@@ -268,7 +268,7 @@
}
@Test
- @Config(shadows = {ShadowFlutterJNI.class})
+ @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void createPlatformViewMessage__throwsIfViewIsNull() {
PlatformViewsController platformViewsController = new PlatformViewsController();
@@ -296,7 +296,7 @@
}
@Test
- @Config(shadows = {ShadowFlutterJNI.class})
+ @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void onDetachedFromJNI_clearsPlatformViewContext() {
PlatformViewsController platformViewsController = new PlatformViewsController();
@@ -326,7 +326,7 @@
}
@Test
- @Config(shadows = {ShadowFlutterJNI.class})
+ @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void onPreEngineRestart_clearsPlatformViewContext() {
PlatformViewsController platformViewsController = new PlatformViewsController();
@@ -356,7 +356,7 @@
}
@Test
- @Config(shadows = {ShadowFlutterJNI.class})
+ @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void createPlatformViewMessage__throwsIfViewHasParent() {
PlatformViewsController platformViewsController = new PlatformViewsController();
@@ -386,7 +386,7 @@
}
@Test
- @Config(shadows = {ShadowFlutterJNI.class})
+ @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void disposeAndroidView__hybridComposition() {
PlatformViewsController platformViewsController = new PlatformViewsController();
@@ -427,7 +427,12 @@
}
@Test
- @Config(shadows = {ShadowFlutterSurfaceView.class, ShadowFlutterJNI.class})
+ @Config(
+ shadows = {
+ ShadowFlutterSurfaceView.class,
+ ShadowFlutterJNI.class,
+ ShadowPlatformTaskQueue.class
+ })
public void onEndFrame__destroysOverlaySurfaceAfterFrameOnFlutterSurfaceView() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
@@ -525,7 +530,12 @@
}
@Test
- @Config(shadows = {ShadowFlutterSurfaceView.class, ShadowFlutterJNI.class})
+ @Config(
+ shadows = {
+ ShadowFlutterSurfaceView.class,
+ ShadowFlutterJNI.class,
+ ShadowPlatformTaskQueue.class
+ })
public void onEndFrame__removesPlatformViewParent() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
@@ -563,7 +573,12 @@
}
@Test
- @Config(shadows = {ShadowFlutterSurfaceView.class, ShadowFlutterJNI.class})
+ @Config(
+ shadows = {
+ ShadowFlutterSurfaceView.class,
+ ShadowFlutterJNI.class,
+ ShadowPlatformTaskQueue.class
+ })
public void detach__destroysOverlaySurfaces() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
@@ -695,7 +710,7 @@
}
@Test
- @Config(shadows = {ShadowFlutterJNI.class})
+ @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void convertPlatformViewRenderSurfaceAsDefault() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
@@ -741,7 +756,7 @@
}
@Test
- @Config(shadows = {ShadowFlutterJNI.class})
+ @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void dontConverRenderSurfaceWhenFlagIsTrue() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
@@ -812,7 +827,10 @@
new MethodCall("create", platformViewCreateArguments);
jni.handlePlatformMessage(
- "flutter/platform_views", encodeMethodCall(platformCreateMethodCall), /*replyId=*/ 0);
+ "flutter/platform_views",
+ encodeMethodCall(platformCreateMethodCall),
+ /*replyId=*/ 0,
+ /*messageData=*/ 0);
}
private static void disposePlatformView(
@@ -826,7 +844,10 @@
new MethodCall("dispose", platformViewDisposeArguments);
jni.handlePlatformMessage(
- "flutter/platform_views", encodeMethodCall(platformDisposeMethodCall), /*replyId=*/ 0);
+ "flutter/platform_views",
+ encodeMethodCall(platformDisposeMethodCall),
+ /*replyId=*/ 0,
+ /*messageData=*/ 0);
}
private static void synchronizeToNativeViewHierarchy(
@@ -835,7 +856,10 @@
final MethodCall convertMethodCall = new MethodCall("synchronizeToNativeViewHierarchy", yes);
jni.handlePlatformMessage(
- "flutter/platform_views", encodeMethodCall(convertMethodCall), /*replyId=*/ 0);
+ "flutter/platform_views",
+ encodeMethodCall(convertMethodCall),
+ /*replyId=*/ 0,
+ /*messageData=*/ 0);
}
private static FlutterView attach(
@@ -901,6 +925,20 @@
return view;
}
+ /**
+ * For convenience when writing tests, this allows us to make fake messages from Flutter via
+ * Platform Channels. Typically those calls happen on the ui thread which dispatches to the
+ * platform thread. Since tests run on the platform thread it makes it difficult to test without
+ * this, but isn't technically required.
+ */
+ @Implements(io.flutter.embedding.engine.dart.PlatformTaskQueue.class)
+ public static class ShadowPlatformTaskQueue {
+ @Implementation
+ public void dispatch(Runnable runnable) {
+ runnable.run();
+ }
+ }
+
@Implements(FlutterJNI.class)
public static class ShadowFlutterJNI {
private static SparseArray<ByteBuffer> replies = new SparseArray<>();
diff --git a/testing/android_background_image/android/app/src/main/java/dev/flutter/android_background_image/MainActivity.java b/testing/android_background_image/android/app/src/main/java/dev/flutter/android_background_image/MainActivity.java
index 7300178..57d62e9 100644
--- a/testing/android_background_image/android/app/src/main/java/dev/flutter/android_background_image/MainActivity.java
+++ b/testing/android_background_image/android/app/src/main/java/dev/flutter/android_background_image/MainActivity.java
@@ -31,7 +31,10 @@
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
- flutterEngine.getDartExecutor().getBinaryMessenger().setMessageHandler("finish", finishHandler);
+ flutterEngine
+ .getDartExecutor()
+ .getBinaryMessenger()
+ .setMessageHandler("finish", finishHandler, null);
final boolean moved = moveTaskToBack(true);
if (!moved) {
diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java
index fbce67d..6599ed5 100644
--- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java
+++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java
@@ -22,7 +22,8 @@
secondEngine
.getDartExecutor()
- .setMessageHandler("take_screenshot", (byteBuffer, binaryReply) -> notifyFlutterRendered());
+ .setMessageHandler(
+ "take_screenshot", (byteBuffer, binaryReply) -> notifyFlutterRendered(), null);
return secondEngine;
}
diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestableFlutterActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestableFlutterActivity.java
index 82ba2d9..75a5240 100644
--- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestableFlutterActivity.java
+++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestableFlutterActivity.java
@@ -19,7 +19,8 @@
// registration will fail and print a scary exception in the logs.
flutterEngine
.getDartExecutor()
- .setMessageHandler("take_screenshot", (byteBuffer, binaryReply) -> notifyFlutterRendered());
+ .setMessageHandler(
+ "take_screenshot", (byteBuffer, binaryReply) -> notifyFlutterRendered(), null);
}
protected void notifyFlutterRendered() {