From fad9ae8e43ad37426069ddc901c9acbf8b9c8159 Mon Sep 17 00:00:00 2001 From: xster Date: Wed, 27 Jan 2021 13:30:23 -0800 Subject: [PATCH] Load FlutterLoader when creating FlutterEngineGroup (#23980) --- .../embedding/engine/FlutterEngineGroup.java | 29 +++++++++--- .../FlutterEngineGroupComponentTest.java | 45 +++++++++++++++---- .../scenarios/SpawnedEngineActivity.java | 2 +- 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java index 0e9d76350..68389cfad 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 android.content.Context; 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 @@ import java.util.List; * 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 class FlutterEngineGroup { 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 8e709d86c..42e129650 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.junit.Assert.assertEquals; 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.RobolectricTestRunner; 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 class FlutterEngineGroupComponentTest { 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 0d8c2fe83..52ab54b8e 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); -- GitLab