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() {