diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 114d8e8947029..ef8b87dbc92b9 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -149,7 +149,8 @@ * *
{@code
  * // Create and pre-warm a FlutterEngine.
- * FlutterEngine flutterEngine = new FlutterEngine(context);
+ * FlutterEngineGroup group = new FlutterEngineGroup(context);
+ * FlutterEngine flutterEngine = group.createAndRunDefaultEngine(context);
  * flutterEngine.getDartExecutor().executeDartEntrypoint(DartEntrypoint.createDefault());
  *
  * // Cache the pre-warmed FlutterEngine in the FlutterEngineCache.
diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
index f91800314f5ce..4a2c7fa4b8a89 100644
--- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
+++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
@@ -90,6 +90,7 @@ public interface DelegateFactory {
   private boolean isFirstFrameRendered;
   private boolean isAttached;
   private Integer previousVisibility;
+  @Nullable private FlutterEngineGroup engineGroup;
 
   @NonNull
   private final FlutterUiDisplayListener flutterUiDisplayListener =
@@ -109,8 +110,15 @@ public void onFlutterUiNoLongerDisplayed() {
       };
 
   FlutterActivityAndFragmentDelegate(@NonNull Host host) {
+    this(host, null);
+  }
+
+  FlutterActivityAndFragmentDelegate(@NonNull Host host, @Nullable FlutterEngineGroup engineGroup) {
     this.host = host;
     this.isFirstFrameRendered = false;
+    if (engineGroup != null) {
+      this.engineGroup = engineGroup;
+    }
   }
 
   /**
@@ -298,12 +306,16 @@ void onAttach(@NonNull Context context) {
         TAG,
         "No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
             + " this FlutterFragment.");
+
+    FlutterEngineGroup group =
+        engineGroup == null
+            ? new FlutterEngineGroup(host.getContext(), host.getFlutterShellArgs().toArray())
+            : engineGroup;
     flutterEngine =
-        new FlutterEngine(
-            host.getContext(),
-            host.getFlutterShellArgs().toArray(),
-            /*automaticallyRegisterPlugins=*/ false,
-            /*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());
+        group.createAndRunEngine(
+            new FlutterEngineGroup.Options(host.getContext())
+                .setAutomaticallyRegisterPlugins(false)
+                .setWaitForRestorationData(host.shouldRestoreAndSaveState()));
     isFlutterEngineFromHost = false;
   }
 
diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java
index 46264e7be8dff..0ef5d872358d9 100644
--- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java
+++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java
@@ -79,7 +79,8 @@
  *
  * 
{@code
  * // Create and pre-warm a FlutterEngine.
- * FlutterEngine flutterEngine = new FlutterEngine(context);
+ * FlutterEngineGroup group = new FlutterEngineGroup(context);
+ * FlutterEngine flutterEngine = group.createAndRunDefaultEngine(context);
  * flutterEngine
  *   .getDartExecutor()
  *   .executeDartEntrypoint(DartEntrypoint.createDefault());
diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
index 8fb3bf2a6a37c..bc2902c0281a9 100644
--- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
+++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
@@ -9,6 +9,7 @@
 import android.content.res.AssetManager;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import io.flutter.FlutterInjector;
 import io.flutter.Log;
 import io.flutter.embedding.engine.dart.DartExecutor;
@@ -279,6 +280,27 @@ public FlutterEngine(
       @Nullable String[] dartVmArgs,
       boolean automaticallyRegisterPlugins,
       boolean waitForRestorationData) {
+    this(
+        context,
+        flutterLoader,
+        flutterJNI,
+        platformViewsController,
+        dartVmArgs,
+        automaticallyRegisterPlugins,
+        waitForRestorationData,
+        null);
+  }
+
+  @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+  public FlutterEngine(
+      @NonNull Context context,
+      @Nullable FlutterLoader flutterLoader,
+      @NonNull FlutterJNI flutterJNI,
+      @NonNull PlatformViewsController platformViewsController,
+      @Nullable String[] dartVmArgs,
+      boolean automaticallyRegisterPlugins,
+      boolean waitForRestorationData,
+      @Nullable FlutterEngineGroup group) {
     AssetManager assetManager;
     try {
       assetManager = context.createPackageContext(context.getPackageName(), 0).getAssets();
@@ -347,7 +369,8 @@ public FlutterEngine(
     this.platformViewsController.onAttachedToJNI();
 
     this.pluginRegistry =
-        new FlutterEngineConnectionRegistry(context.getApplicationContext(), this, flutterLoader);
+        new FlutterEngineConnectionRegistry(
+            context.getApplicationContext(), this, flutterLoader, group);
 
     localizationPlugin.sendLocalesToFlutter(context.getResources().getConfiguration());
 
diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java
index 80aab73e04959..e2bc8b54d9ee4 100644
--- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java
+++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java
@@ -97,7 +97,8 @@
   FlutterEngineConnectionRegistry(
       @NonNull Context appContext,
       @NonNull FlutterEngine flutterEngine,
-      @NonNull FlutterLoader flutterLoader) {
+      @NonNull FlutterLoader flutterLoader,
+      @Nullable FlutterEngineGroup group) {
     this.flutterEngine = flutterEngine;
     pluginBinding =
         new FlutterPlugin.FlutterPluginBinding(
@@ -106,7 +107,8 @@
             flutterEngine.getDartExecutor(),
             flutterEngine.getRenderer(),
             flutterEngine.getPlatformViewsController().getRegistry(),
-            new DefaultFlutterAssets(flutterLoader));
+            new DefaultFlutterAssets(flutterLoader),
+            group);
   }
 
   public void destroy() {
diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java
index fc9a95c2dd7bf..c45210f4f1063 100644
--- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java
+++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java
@@ -210,7 +210,8 @@ public void onEngineWillDestroy() {
         platformViewsController, // PlatformViewsController.
         null, // String[]. The Dart VM has already started, this arguments will have no effect.
         automaticallyRegisterPlugins, // boolean.
-        waitForRestorationData); // boolean.
+        waitForRestorationData, // boolean.
+        this);
   }
 
   /** Options that control how a FlutterEngine should be created. */
diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java b/shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java
index c9ed009a44ef1..055126f0ff3c7 100644
--- a/shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java
+++ b/shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java
@@ -6,8 +6,10 @@
 
 import android.content.Context;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.lifecycle.Lifecycle;
 import io.flutter.embedding.engine.FlutterEngine;
+import io.flutter.embedding.engine.FlutterEngineGroup;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.platform.PlatformViewRegistry;
 import io.flutter.view.TextureRegistry;
@@ -107,6 +109,7 @@ class FlutterPluginBinding {
     private final TextureRegistry textureRegistry;
     private final PlatformViewRegistry platformViewRegistry;
     private final FlutterAssets flutterAssets;
+    private final FlutterEngineGroup group;
 
     public FlutterPluginBinding(
         @NonNull Context applicationContext,
@@ -114,13 +117,15 @@ public FlutterPluginBinding(
         @NonNull BinaryMessenger binaryMessenger,
         @NonNull TextureRegistry textureRegistry,
         @NonNull PlatformViewRegistry platformViewRegistry,
-        @NonNull FlutterAssets flutterAssets) {
+        @NonNull FlutterAssets flutterAssets,
+        @Nullable FlutterEngineGroup group) {
       this.applicationContext = applicationContext;
       this.flutterEngine = flutterEngine;
       this.binaryMessenger = binaryMessenger;
       this.textureRegistry = textureRegistry;
       this.platformViewRegistry = platformViewRegistry;
       this.flutterAssets = flutterAssets;
+      this.group = group;
     }
 
     @NonNull
@@ -157,6 +162,21 @@ public PlatformViewRegistry getPlatformViewRegistry() {
     public FlutterAssets getFlutterAssets() {
       return flutterAssets;
     }
+
+    /**
+     * Accessor for the {@link FlutterEngineGroup} used to create the {@link FlutterEngine} for the
+     * app.
+     *
+     * 

This is useful in the rare case that a plugin has to spawn its own engine (for example, + * running an engine the background). The result is nullable since old versions of Flutter and + * custom setups may not have used a {@link FlutterEngineGroup}. Failing to use this when it is + * available will result in suboptimal performance and odd behaviors related to Dart isolate + * groups. + */ + @Nullable + public FlutterEngineGroup getEngineGroup() { + return group; + } } /** Provides Flutter plugins with access to Flutter asset information. */ diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java index b5db78ab35dd0..ed41d1d65f307 100644 --- a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java +++ b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java @@ -25,7 +25,8 @@ * *

  * // Create the FlutterEngine that will back the Flutter UI.
- * FlutterEngine flutterEngine = new FlutterEngine(context);
+ * FlutterEngineGroup group = new FlutterEngineGroup(context);
+ * FlutterEngine flutterEngine = group.createAndRunDefaultEngine(context);
  *
  * // Create a ShimPluginRegistry and wrap the FlutterEngine with the shim.
  * ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine, platformViewsController);
diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java
index 72441434b2f54..d539f3b40dbb7 100644
--- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java
@@ -1159,6 +1159,22 @@ public void itDoesNotDelayTheFirstDrawWhenRequestedAndWithAProvidedSplashScreen(
     assertNull(delegate.activePreDrawListener);
   }
 
+  @Test
+  public void usesFlutterEngineGroup() {
+    FlutterEngineGroup mockEngineGroup = mock(FlutterEngineGroup.class);
+    when(mockEngineGroup.createAndRunEngine(any(FlutterEngineGroup.Options.class)))
+        .thenReturn(mockFlutterEngine);
+    FlutterActivityAndFragmentDelegate.Host host =
+        mock(FlutterActivityAndFragmentDelegate.Host.class);
+    when(mockHost.getContext()).thenReturn(ctx);
+
+    FlutterActivityAndFragmentDelegate delegate =
+        new FlutterActivityAndFragmentDelegate(mockHost, mockEngineGroup);
+    delegate.onAttach(ctx);
+    FlutterEngine engineUnderTest = delegate.getFlutterEngine();
+    assertEquals(engineUnderTest, mockFlutterEngine);
+  }
+
   /**
    * Creates a mock {@link io.flutter.embedding.engine.FlutterEngine}.
    *
diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java
index 975d6d052a5b1..8e5c21a98d5be 100644
--- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java
@@ -40,7 +40,7 @@ public void itDoesNotRegisterTheSamePluginTwice() {
     FakeFlutterPlugin fakePlugin2 = new FakeFlutterPlugin();
 
     FlutterEngineConnectionRegistry registry =
-        new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader);
+        new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader, null);
 
     // Verify that the registry doesn't think it contains our plugin yet.
     assertFalse(registry.has(fakePlugin1.getClass()));
@@ -86,7 +86,7 @@ public void activityResultListenerCanBeRemovedFromListener() {
 
     // Set up the environment to get the required internal data
     FlutterEngineConnectionRegistry registry =
-        new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader);
+        new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader, null);
     FakeActivityAwareFlutterPlugin fakePlugin = new FakeActivityAwareFlutterPlugin();
     registry.add(fakePlugin);
     registry.attachToActivity(appComponent, lifecycle);
@@ -129,7 +129,7 @@ public void softwareRendering() {
 
     // Test attachToActivity with an Activity that has no Intent.
     FlutterEngineConnectionRegistry registry =
-        new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader);
+        new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader, null);
     registry.attachToActivity(appComponent, mock(Lifecycle.class));
     verify(platformViewsController).setSoftwareRendering(false);
 
diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java
index 308ab1d07cf56..cc7a8e5fee999 100644
--- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java
@@ -25,8 +25,11 @@
 import io.flutter.FlutterInjector;
 import io.flutter.embedding.engine.FlutterEngine;
 import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener;
+import io.flutter.embedding.engine.FlutterEngineGroup;
 import io.flutter.embedding.engine.FlutterJNI;
 import io.flutter.embedding.engine.loader.FlutterLoader;
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
+import io.flutter.embedding.engine.plugins.PluginRegistry;
 import io.flutter.plugin.platform.PlatformViewsController;
 import io.flutter.plugins.GeneratedPluginRegistrant;
 import java.util.List;
@@ -323,4 +326,34 @@ public void itComesWithARunningDartExecutorIfJNIIsAlreadyAttached() throws NameN
 
     assertTrue(engineUnderTest.getDartExecutor().isExecutingDart());
   }
+
+  @Test
+  public void passesEngineGroupToPlugins() throws NameNotFoundException {
+    Context packageContext = mock(Context.class);
+
+    when(mockContext.createPackageContext(any(), anyInt())).thenReturn(packageContext);
+    when(flutterJNI.isAttached()).thenReturn(true);
+
+    FlutterEngineGroup mockGroup = mock(FlutterEngineGroup.class);
+
+    FlutterEngine engineUnderTest =
+        new FlutterEngine(
+            mockContext,
+            mock(FlutterLoader.class),
+            flutterJNI,
+            new PlatformViewsController(),
+            /*dartVmArgs=*/ new String[] {},
+            /*automaticallyRegisterPlugins=*/ false,
+            /*waitForRestorationData=*/ false,
+            mockGroup);
+
+    PluginRegistry registry = engineUnderTest.getPlugins();
+    FlutterPlugin mockPlugin = mock(FlutterPlugin.class);
+    ArgumentCaptor pluginBindingCaptor =
+        ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class);
+    registry.add(mockPlugin);
+    verify(mockPlugin).onAttachedToEngine(pluginBindingCaptor.capture());
+    assertNotNull(pluginBindingCaptor.getValue());
+    assertEquals(mockGroup, pluginBindingCaptor.getValue().getEngineGroup());
+  }
 }