diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java
index 0e9d76350d00f..68389cfade641 100644
--- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java
+++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java
@@ -8,7 +8,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;
+import io.flutter.embedding.engine.loader.FlutterLoader;
import java.util.ArrayList;
import java.util.List;
@@ -25,6 +27,9 @@
* io.flutter.embedding.engine.FlutterEngine}s are created, resources from an existing living {@link
* io.flutter.embedding.engine.FlutterEngine} is re-used.
*
+ *
The shared resources are kept until the last surviving {@link
+ * io.flutter.embedding.engine.FlutterEngine} is destroyed.
+ *
*
Deleting a FlutterEngineGroup doesn't invalidate its existing {@link
* io.flutter.embedding.engine.FlutterEngine}s, but it eliminates the possibility to create more
* {@link io.flutter.embedding.engine.FlutterEngine}s in that group.
@@ -33,6 +38,23 @@ public class FlutterEngineGroup {
/* package */ @VisibleForTesting final List activeEngines = new ArrayList<>();
+ /** Create a FlutterEngineGroup whose child engines will share resources. */
+ public FlutterEngineGroup(@NonNull Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Create a FlutterEngineGroup whose child engines will share resources. Use {@code dartVmArgs} to
+ * pass flags to the Dart VM during initialization.
+ */
+ public FlutterEngineGroup(@NonNull Context context, @Nullable String[] dartVmArgs) {
+ FlutterLoader loader = FlutterInjector.instance().flutterLoader();
+ if (!loader.initialized()) {
+ loader.startInitialization(context.getApplicationContext());
+ loader.ensureInitializationComplete(context, dartVmArgs);
+ }
+ }
+
/**
* Creates a {@link io.flutter.embedding.engine.FlutterEngine} in this group and run its {@link
* io.flutter.embedding.engine.dart.DartExecutor} with a default entrypoint of the "main" function
@@ -67,18 +89,13 @@ public FlutterEngine createAndRunDefaultEngine(@NonNull Context context) {
public FlutterEngine createAndRunEngine(
@NonNull Context context, @Nullable DartEntrypoint dartEntrypoint) {
FlutterEngine engine = null;
- // This is done up here because an engine needs to be created first in order to be able to use
- // DartEntrypoint.createDefault. The engine creation initializes the FlutterLoader so
- // DartEntrypoint known where to find the assets for the AOT or kernel code.
- if (activeEngines.size() == 0) {
- engine = createEngine(context);
- }
if (dartEntrypoint == null) {
dartEntrypoint = DartEntrypoint.createDefault();
}
if (activeEngines.size() == 0) {
+ engine = createEngine(context);
engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint);
} else {
engine = activeEngines.get(0).spawn(context, dartEntrypoint);
diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java
index 8e709d86c43c1..42e1296505e5a 100644
--- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java
@@ -8,12 +8,17 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
-import io.flutter.embedding.engine.dart.DartExecutor;
+import android.content.res.AssetManager;
+import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;
import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.plugins.GeneratedPluginRegistrant;
@@ -27,34 +32,41 @@
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
-// It's a component test because it tests both FlutterEngineGroup and FlutterEngine.
+// It's a component test because it tests the FlutterEngineGroup its components such as the
+// FlutterEngine and the DartExecutor.
@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class FlutterEngineGroupComponentTest {
- @Mock FlutterJNI flutterJNI;
+ @Mock FlutterJNI mockflutterJNI;
+ @Mock FlutterLoader mockFlutterLoader;
FlutterEngineGroup engineGroupUnderTest;
FlutterEngine firstEngineUnderTest;
boolean jniAttached;
@Before
public void setUp() {
+ FlutterInjector.reset();
+
MockitoAnnotations.initMocks(this);
jniAttached = false;
- when(flutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
- doAnswer(invocation -> jniAttached = true).when(flutterJNI).attachToNative(false);
+ when(mockflutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
+ doAnswer(invocation -> jniAttached = true).when(mockflutterJNI).attachToNative(false);
GeneratedPluginRegistrant.clearRegisteredEngines();
+ when(mockFlutterLoader.findAppBundlePath()).thenReturn("some/path/to/flutter_assets");
+ FlutterInjector.setInstance(
+ new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
+
firstEngineUnderTest =
spy(
new FlutterEngine(
RuntimeEnvironment.application,
mock(FlutterLoader.class),
- flutterJNI,
+ mockflutterJNI,
/*dartVmArgs=*/ new String[] {},
/*automaticallyRegisterPlugins=*/ false));
- when(firstEngineUnderTest.getDartExecutor()).thenReturn(mock(DartExecutor.class));
engineGroupUnderTest =
- new FlutterEngineGroup() {
+ new FlutterEngineGroup(RuntimeEnvironment.application) {
@Override
FlutterEngine createEngine(Context context) {
return firstEngineUnderTest;
@@ -127,4 +139,21 @@ public void canSpawnMoreEngines() {
RuntimeEnvironment.application, mock(DartEntrypoint.class));
assertEquals(2, engineGroupUnderTest.activeEngines.size());
}
+
+ @Test
+ public void canCreateAndRunCustomEntrypoints() {
+ FlutterEngine firstEngine =
+ engineGroupUnderTest.createAndRunEngine(
+ RuntimeEnvironment.application,
+ new DartEntrypoint(
+ FlutterInjector.instance().flutterLoader().findAppBundlePath(),
+ "other entrypoint"));
+ assertEquals(1, engineGroupUnderTest.activeEngines.size());
+ verify(mockflutterJNI, times(1))
+ .runBundleAndSnapshotFromLibrary(
+ eq("some/path/to/flutter_assets"),
+ eq("other entrypoint"),
+ isNull(String.class),
+ any(AssetManager.class));
+ }
}
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 0d8c2fe831a75..52ab54b8e3a9e 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
@@ -14,7 +14,7 @@ public class SpawnedEngineActivity extends TestActivity {
@Override
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
- FlutterEngineGroup engineGroup = new FlutterEngineGroup();
+ FlutterEngineGroup engineGroup = new FlutterEngineGroup(context);
engineGroup.createAndRunDefaultEngine(context);
FlutterEngine secondEngine = engineGroup.createAndRunDefaultEngine(context);