Skip to content

Commit fad9ae8

Browse files
authored
Load FlutterLoader when creating FlutterEngineGroup (flutter#23980)
1 parent f6280f2 commit fad9ae8

File tree

3 files changed

+61
-15
lines changed

3 files changed

+61
-15
lines changed

shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import androidx.annotation.NonNull;
99
import androidx.annotation.Nullable;
1010
import androidx.annotation.VisibleForTesting;
11+
import io.flutter.FlutterInjector;
1112
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;
13+
import io.flutter.embedding.engine.loader.FlutterLoader;
1214
import java.util.ArrayList;
1315
import java.util.List;
1416

@@ -25,6 +27,9 @@
2527
* io.flutter.embedding.engine.FlutterEngine}s are created, resources from an existing living {@link
2628
* io.flutter.embedding.engine.FlutterEngine} is re-used.
2729
*
30+
* <p>The shared resources are kept until the last surviving {@link
31+
* io.flutter.embedding.engine.FlutterEngine} is destroyed.
32+
*
2833
* <p>Deleting a FlutterEngineGroup doesn't invalidate its existing {@link
2934
* io.flutter.embedding.engine.FlutterEngine}s, but it eliminates the possibility to create more
3035
* {@link io.flutter.embedding.engine.FlutterEngine}s in that group.
@@ -33,6 +38,23 @@ public class FlutterEngineGroup {
3338

3439
/* package */ @VisibleForTesting final List<FlutterEngine> activeEngines = new ArrayList<>();
3540

41+
/** Create a FlutterEngineGroup whose child engines will share resources. */
42+
public FlutterEngineGroup(@NonNull Context context) {
43+
this(context, null);
44+
}
45+
46+
/**
47+
* Create a FlutterEngineGroup whose child engines will share resources. Use {@code dartVmArgs} to
48+
* pass flags to the Dart VM during initialization.
49+
*/
50+
public FlutterEngineGroup(@NonNull Context context, @Nullable String[] dartVmArgs) {
51+
FlutterLoader loader = FlutterInjector.instance().flutterLoader();
52+
if (!loader.initialized()) {
53+
loader.startInitialization(context.getApplicationContext());
54+
loader.ensureInitializationComplete(context, dartVmArgs);
55+
}
56+
}
57+
3658
/**
3759
* Creates a {@link io.flutter.embedding.engine.FlutterEngine} in this group and run its {@link
3860
* io.flutter.embedding.engine.dart.DartExecutor} with a default entrypoint of the "main" function
@@ -67,18 +89,13 @@ public FlutterEngine createAndRunDefaultEngine(@NonNull Context context) {
6789
public FlutterEngine createAndRunEngine(
6890
@NonNull Context context, @Nullable DartEntrypoint dartEntrypoint) {
6991
FlutterEngine engine = null;
70-
// This is done up here because an engine needs to be created first in order to be able to use
71-
// DartEntrypoint.createDefault. The engine creation initializes the FlutterLoader so
72-
// DartEntrypoint known where to find the assets for the AOT or kernel code.
73-
if (activeEngines.size() == 0) {
74-
engine = createEngine(context);
75-
}
7692

7793
if (dartEntrypoint == null) {
7894
dartEntrypoint = DartEntrypoint.createDefault();
7995
}
8096

8197
if (activeEngines.size() == 0) {
98+
engine = createEngine(context);
8299
engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint);
83100
} else {
84101
engine = activeEngines.get(0).spawn(context, dartEntrypoint);

shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88
import static org.mockito.Mockito.any;
99
import static org.mockito.Mockito.doAnswer;
1010
import static org.mockito.Mockito.doReturn;
11+
import static org.mockito.Mockito.eq;
12+
import static org.mockito.Mockito.isNull;
1113
import static org.mockito.Mockito.mock;
1214
import static org.mockito.Mockito.spy;
15+
import static org.mockito.Mockito.times;
16+
import static org.mockito.Mockito.verify;
1317
import static org.mockito.Mockito.when;
1418

1519
import android.content.Context;
16-
import io.flutter.embedding.engine.dart.DartExecutor;
20+
import android.content.res.AssetManager;
21+
import io.flutter.FlutterInjector;
1722
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;
1823
import io.flutter.embedding.engine.loader.FlutterLoader;
1924
import io.flutter.plugins.GeneratedPluginRegistrant;
@@ -27,34 +32,41 @@
2732
import org.robolectric.RuntimeEnvironment;
2833
import org.robolectric.annotation.Config;
2934

30-
// It's a component test because it tests both FlutterEngineGroup and FlutterEngine.
35+
// It's a component test because it tests the FlutterEngineGroup its components such as the
36+
// FlutterEngine and the DartExecutor.
3137
@Config(manifest = Config.NONE)
3238
@RunWith(RobolectricTestRunner.class)
3339
public class FlutterEngineGroupComponentTest {
34-
@Mock FlutterJNI flutterJNI;
40+
@Mock FlutterJNI mockflutterJNI;
41+
@Mock FlutterLoader mockFlutterLoader;
3542
FlutterEngineGroup engineGroupUnderTest;
3643
FlutterEngine firstEngineUnderTest;
3744
boolean jniAttached;
3845

3946
@Before
4047
public void setUp() {
48+
FlutterInjector.reset();
49+
4150
MockitoAnnotations.initMocks(this);
4251
jniAttached = false;
43-
when(flutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
44-
doAnswer(invocation -> jniAttached = true).when(flutterJNI).attachToNative(false);
52+
when(mockflutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
53+
doAnswer(invocation -> jniAttached = true).when(mockflutterJNI).attachToNative(false);
4554
GeneratedPluginRegistrant.clearRegisteredEngines();
4655

56+
when(mockFlutterLoader.findAppBundlePath()).thenReturn("some/path/to/flutter_assets");
57+
FlutterInjector.setInstance(
58+
new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
59+
4760
firstEngineUnderTest =
4861
spy(
4962
new FlutterEngine(
5063
RuntimeEnvironment.application,
5164
mock(FlutterLoader.class),
52-
flutterJNI,
65+
mockflutterJNI,
5366
/*dartVmArgs=*/ new String[] {},
5467
/*automaticallyRegisterPlugins=*/ false));
55-
when(firstEngineUnderTest.getDartExecutor()).thenReturn(mock(DartExecutor.class));
5668
engineGroupUnderTest =
57-
new FlutterEngineGroup() {
69+
new FlutterEngineGroup(RuntimeEnvironment.application) {
5870
@Override
5971
FlutterEngine createEngine(Context context) {
6072
return firstEngineUnderTest;
@@ -127,4 +139,21 @@ public void canSpawnMoreEngines() {
127139
RuntimeEnvironment.application, mock(DartEntrypoint.class));
128140
assertEquals(2, engineGroupUnderTest.activeEngines.size());
129141
}
142+
143+
@Test
144+
public void canCreateAndRunCustomEntrypoints() {
145+
FlutterEngine firstEngine =
146+
engineGroupUnderTest.createAndRunEngine(
147+
RuntimeEnvironment.application,
148+
new DartEntrypoint(
149+
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
150+
"other entrypoint"));
151+
assertEquals(1, engineGroupUnderTest.activeEngines.size());
152+
verify(mockflutterJNI, times(1))
153+
.runBundleAndSnapshotFromLibrary(
154+
eq("some/path/to/flutter_assets"),
155+
eq("other entrypoint"),
156+
isNull(String.class),
157+
any(AssetManager.class));
158+
}
130159
}

testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class SpawnedEngineActivity extends TestActivity {
1414

1515
@Override
1616
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
17-
FlutterEngineGroup engineGroup = new FlutterEngineGroup();
17+
FlutterEngineGroup engineGroup = new FlutterEngineGroup(context);
1818
engineGroup.createAndRunDefaultEngine(context);
1919

2020
FlutterEngine secondEngine = engineGroup.createAndRunDefaultEngine(context);

0 commit comments

Comments
 (0)