Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 725d47d

Browse files
authored
reland: Started using FlutterEngineGroups by default on Android (#38367)
* Revert "Revert "Started using FlutterEngineGroups by default on Android (#37822)" (#38351)" This reverts commit 13ae6eb. * fixed the entrypoint test * updated old test * coldpalelight's suggestion * added entrypoint args
1 parent 24a6a91 commit 725d47d

11 files changed

+154
-27
lines changed

shell/platform/android/io/flutter/embedding/android/FlutterActivity.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@
149149
*
150150
* <pre>{@code
151151
* // Create and pre-warm a FlutterEngine.
152-
* FlutterEngine flutterEngine = new FlutterEngine(context);
152+
* FlutterEngineGroup group = new FlutterEngineGroup(context);
153+
* FlutterEngine flutterEngine = group.createAndRunDefaultEngine(context);
153154
* flutterEngine.getDartExecutor().executeDartEntrypoint(DartEntrypoint.createDefault());
154155
*
155156
* // Cache the pre-warmed FlutterEngine in the FlutterEngineCache.

shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java

+32-14
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public interface DelegateFactory {
9090
private boolean isFirstFrameRendered;
9191
private boolean isAttached;
9292
private Integer previousVisibility;
93+
@Nullable private FlutterEngineGroup engineGroup;
9394

9495
@NonNull
9596
private final FlutterUiDisplayListener flutterUiDisplayListener =
@@ -109,8 +110,13 @@ public void onFlutterUiNoLongerDisplayed() {
109110
};
110111

111112
FlutterActivityAndFragmentDelegate(@NonNull Host host) {
113+
this(host, null);
114+
}
115+
116+
FlutterActivityAndFragmentDelegate(@NonNull Host host, @Nullable FlutterEngineGroup engineGroup) {
112117
this.host = host;
113118
this.isFirstFrameRendered = false;
119+
this.engineGroup = engineGroup;
114120
}
115121

116122
/**
@@ -220,6 +226,21 @@ void onAttach(@NonNull Context context) {
220226
return activity;
221227
}
222228

229+
private FlutterEngineGroup.Options addEntrypointOptions(FlutterEngineGroup.Options options) {
230+
String appBundlePathOverride = host.getAppBundlePath();
231+
if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
232+
appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
233+
}
234+
235+
DartExecutor.DartEntrypoint dartEntrypoint =
236+
new DartExecutor.DartEntrypoint(
237+
appBundlePathOverride, host.getDartEntrypointFunctionName());
238+
return options
239+
.setDartEntrypoint(dartEntrypoint)
240+
.setInitialRoute(host.getInitialRoute())
241+
.setDartEntrypointArgs(host.getDartEntrypointArgs());
242+
}
243+
223244
/**
224245
* Obtains a reference to a FlutterEngine to back this delegate and its {@code host}.
225246
*
@@ -277,17 +298,9 @@ void onAttach(@NonNull Context context) {
277298
+ "'");
278299
}
279300

280-
String appBundlePathOverride = host.getAppBundlePath();
281-
if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
282-
appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
283-
}
284-
285-
DartExecutor.DartEntrypoint dartEntrypoint =
286-
new DartExecutor.DartEntrypoint(
287-
appBundlePathOverride, host.getDartEntrypointFunctionName());
288301
flutterEngine =
289302
flutterEngineGroup.createAndRunEngine(
290-
host.getContext(), dartEntrypoint, host.getInitialRoute());
303+
addEntrypointOptions(new FlutterEngineGroup.Options(host.getContext())));
291304
isFlutterEngineFromHost = false;
292305
return;
293306
}
@@ -298,12 +311,17 @@ void onAttach(@NonNull Context context) {
298311
TAG,
299312
"No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
300313
+ " this FlutterFragment.");
314+
315+
FlutterEngineGroup group =
316+
engineGroup == null
317+
? new FlutterEngineGroup(host.getContext(), host.getFlutterShellArgs().toArray())
318+
: engineGroup;
301319
flutterEngine =
302-
new FlutterEngine(
303-
host.getContext(),
304-
host.getFlutterShellArgs().toArray(),
305-
/*automaticallyRegisterPlugins=*/ false,
306-
/*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());
320+
group.createAndRunEngine(
321+
addEntrypointOptions(
322+
new FlutterEngineGroup.Options(host.getContext())
323+
.setAutomaticallyRegisterPlugins(false)
324+
.setWaitForRestorationData(host.shouldRestoreAndSaveState())));
307325
isFlutterEngineFromHost = false;
308326
}
309327

shell/platform/android/io/flutter/embedding/android/FlutterFragment.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@
7979
*
8080
* <pre>{@code
8181
* // Create and pre-warm a FlutterEngine.
82-
* FlutterEngine flutterEngine = new FlutterEngine(context);
82+
* FlutterEngineGroup group = new FlutterEngineGroup(context);
83+
* FlutterEngine flutterEngine = group.createAndRunDefaultEngine(context);
8384
* flutterEngine
8485
* .getDartExecutor()
8586
* .executeDartEntrypoint(DartEntrypoint.createDefault());

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

+24-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import android.content.res.AssetManager;
1010
import androidx.annotation.NonNull;
1111
import androidx.annotation.Nullable;
12+
import androidx.annotation.VisibleForTesting;
1213
import io.flutter.FlutterInjector;
1314
import io.flutter.Log;
1415
import io.flutter.embedding.engine.dart.DartExecutor;
@@ -279,6 +280,27 @@ public FlutterEngine(
279280
@Nullable String[] dartVmArgs,
280281
boolean automaticallyRegisterPlugins,
281282
boolean waitForRestorationData) {
283+
this(
284+
context,
285+
flutterLoader,
286+
flutterJNI,
287+
platformViewsController,
288+
dartVmArgs,
289+
automaticallyRegisterPlugins,
290+
waitForRestorationData,
291+
null);
292+
}
293+
294+
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
295+
public FlutterEngine(
296+
@NonNull Context context,
297+
@Nullable FlutterLoader flutterLoader,
298+
@NonNull FlutterJNI flutterJNI,
299+
@NonNull PlatformViewsController platformViewsController,
300+
@Nullable String[] dartVmArgs,
301+
boolean automaticallyRegisterPlugins,
302+
boolean waitForRestorationData,
303+
@Nullable FlutterEngineGroup group) {
282304
AssetManager assetManager;
283305
try {
284306
assetManager = context.createPackageContext(context.getPackageName(), 0).getAssets();
@@ -347,7 +369,8 @@ public FlutterEngine(
347369
this.platformViewsController.onAttachedToJNI();
348370

349371
this.pluginRegistry =
350-
new FlutterEngineConnectionRegistry(context.getApplicationContext(), this, flutterLoader);
372+
new FlutterEngineConnectionRegistry(
373+
context.getApplicationContext(), this, flutterLoader, group);
351374

352375
localizationPlugin.sendLocalesToFlutter(context.getResources().getConfiguration());
353376

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@
9797
FlutterEngineConnectionRegistry(
9898
@NonNull Context appContext,
9999
@NonNull FlutterEngine flutterEngine,
100-
@NonNull FlutterLoader flutterLoader) {
100+
@NonNull FlutterLoader flutterLoader,
101+
@Nullable FlutterEngineGroup group) {
101102
this.flutterEngine = flutterEngine;
102103
pluginBinding =
103104
new FlutterPlugin.FlutterPluginBinding(
@@ -106,7 +107,8 @@
106107
flutterEngine.getDartExecutor(),
107108
flutterEngine.getRenderer(),
108109
flutterEngine.getPlatformViewsController().getRegistry(),
109-
new DefaultFlutterAssets(flutterLoader));
110+
new DefaultFlutterAssets(flutterLoader),
111+
group);
110112
}
111113

112114
public void destroy() {

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ public void onEngineWillDestroy() {
210210
platformViewsController, // PlatformViewsController.
211211
null, // String[]. The Dart VM has already started, this arguments will have no effect.
212212
automaticallyRegisterPlugins, // boolean.
213-
waitForRestorationData); // boolean.
213+
waitForRestorationData, // boolean.
214+
this);
214215
}
215216

216217
/** Options that control how a FlutterEngine should be created. */

shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66

77
import android.content.Context;
88
import androidx.annotation.NonNull;
9+
import androidx.annotation.Nullable;
910
import androidx.lifecycle.Lifecycle;
1011
import io.flutter.embedding.engine.FlutterEngine;
12+
import io.flutter.embedding.engine.FlutterEngineGroup;
1113
import io.flutter.plugin.common.BinaryMessenger;
1214
import io.flutter.plugin.platform.PlatformViewRegistry;
1315
import io.flutter.view.TextureRegistry;
@@ -107,20 +109,23 @@ class FlutterPluginBinding {
107109
private final TextureRegistry textureRegistry;
108110
private final PlatformViewRegistry platformViewRegistry;
109111
private final FlutterAssets flutterAssets;
112+
private final FlutterEngineGroup group;
110113

111114
public FlutterPluginBinding(
112115
@NonNull Context applicationContext,
113116
@NonNull FlutterEngine flutterEngine,
114117
@NonNull BinaryMessenger binaryMessenger,
115118
@NonNull TextureRegistry textureRegistry,
116119
@NonNull PlatformViewRegistry platformViewRegistry,
117-
@NonNull FlutterAssets flutterAssets) {
120+
@NonNull FlutterAssets flutterAssets,
121+
@Nullable FlutterEngineGroup group) {
118122
this.applicationContext = applicationContext;
119123
this.flutterEngine = flutterEngine;
120124
this.binaryMessenger = binaryMessenger;
121125
this.textureRegistry = textureRegistry;
122126
this.platformViewRegistry = platformViewRegistry;
123127
this.flutterAssets = flutterAssets;
128+
this.group = group;
124129
}
125130

126131
@NonNull
@@ -157,6 +162,21 @@ public PlatformViewRegistry getPlatformViewRegistry() {
157162
public FlutterAssets getFlutterAssets() {
158163
return flutterAssets;
159164
}
165+
166+
/**
167+
* Accessor for the {@link FlutterEngineGroup} used to create the {@link FlutterEngine} for the
168+
* app.
169+
*
170+
* <p>This is useful in the rare case that a plugin has to spawn its own engine (for example,
171+
* running an engine the background). The result is nullable since old versions of Flutter and
172+
* custom setups may not have used a {@link FlutterEngineGroup}. Failing to use this when it is
173+
* available will result in suboptimal performance and odd behaviors related to Dart isolate
174+
* groups.
175+
*/
176+
@Nullable
177+
public FlutterEngineGroup getEngineGroup() {
178+
return group;
179+
}
160180
}
161181

162182
/** Provides Flutter plugins with access to Flutter asset information. */

shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
*
2626
* <pre>
2727
* // Create the FlutterEngine that will back the Flutter UI.
28-
* FlutterEngine flutterEngine = new FlutterEngine(context);
28+
* FlutterEngineGroup group = new FlutterEngineGroup(context);
29+
* FlutterEngine flutterEngine = group.createAndRunDefaultEngine(context);
2930
*
3031
* // Create a ShimPluginRegistry and wrap the FlutterEngine with the shim.
3132
* ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine, platformViewsController);

shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java

+29-2
Original file line numberDiff line numberDiff line change
@@ -247,10 +247,14 @@ public void itUsesNewEngineInGroupWhenProvided() {
247247
FlutterEngineGroup flutterEngineGroup = mock(FlutterEngineGroup.class);
248248
FlutterEngineGroupCache.getInstance().put("my_flutter_engine_group", flutterEngineGroup);
249249

250+
List<String> entryPointArgs = new ArrayList<>();
251+
entryPointArgs.add("entrypoint-arg");
252+
250253
// Adjust fake host to request cached engine group.
251254
when(mockHost.getCachedEngineGroupId()).thenReturn("my_flutter_engine_group");
252255
when(mockHost.provideFlutterEngine(any(Context.class))).thenReturn(null);
253256
when(mockHost.shouldAttachEngineToActivity()).thenReturn(false);
257+
when(mockHost.getDartEntrypointArgs()).thenReturn(entryPointArgs);
254258

255259
// Create the real object that we're testing.
256260
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
@@ -263,8 +267,15 @@ public void itUsesNewEngineInGroupWhenProvided() {
263267
// event.
264268
// Note: "/fake/path" and "main" come from `setUp()`.
265269
DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint("/fake/path", "main");
266-
verify(flutterEngineGroup, times(1))
267-
.createAndRunEngine(mockHost.getContext(), entrypoint, mockHost.getInitialRoute());
270+
ArgumentCaptor<FlutterEngineGroup.Options> optionsCaptor =
271+
ArgumentCaptor.forClass(FlutterEngineGroup.Options.class);
272+
verify(flutterEngineGroup, times(1)).createAndRunEngine(optionsCaptor.capture());
273+
assertEquals(mockHost.getContext(), optionsCaptor.getValue().getContext());
274+
assertEquals(entrypoint, optionsCaptor.getValue().getDartEntrypoint());
275+
assertEquals(mockHost.getInitialRoute(), optionsCaptor.getValue().getInitialRoute());
276+
assertNotNull(optionsCaptor.getValue().getDartEntrypointArgs());
277+
assertEquals(1, optionsCaptor.getValue().getDartEntrypointArgs().size());
278+
assertEquals("entrypoint-arg", optionsCaptor.getValue().getDartEntrypointArgs().get(0));
268279
}
269280

270281
@Test(expected = IllegalStateException.class)
@@ -1159,6 +1170,22 @@ public void itDoesNotDelayTheFirstDrawWhenRequestedAndWithAProvidedSplashScreen(
11591170
assertNull(delegate.activePreDrawListener);
11601171
}
11611172

1173+
@Test
1174+
public void usesFlutterEngineGroup() {
1175+
FlutterEngineGroup mockEngineGroup = mock(FlutterEngineGroup.class);
1176+
when(mockEngineGroup.createAndRunEngine(any(FlutterEngineGroup.Options.class)))
1177+
.thenReturn(mockFlutterEngine);
1178+
FlutterActivityAndFragmentDelegate.Host host =
1179+
mock(FlutterActivityAndFragmentDelegate.Host.class);
1180+
when(mockHost.getContext()).thenReturn(ctx);
1181+
1182+
FlutterActivityAndFragmentDelegate delegate =
1183+
new FlutterActivityAndFragmentDelegate(mockHost, mockEngineGroup);
1184+
delegate.onAttach(ctx);
1185+
FlutterEngine engineUnderTest = delegate.getFlutterEngine();
1186+
assertEquals(engineUnderTest, mockFlutterEngine);
1187+
}
1188+
11621189
/**
11631190
* Creates a mock {@link io.flutter.embedding.engine.FlutterEngine}.
11641191
*

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public void itDoesNotRegisterTheSamePluginTwice() {
4040
FakeFlutterPlugin fakePlugin2 = new FakeFlutterPlugin();
4141

4242
FlutterEngineConnectionRegistry registry =
43-
new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader);
43+
new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader, null);
4444

4545
// Verify that the registry doesn't think it contains our plugin yet.
4646
assertFalse(registry.has(fakePlugin1.getClass()));
@@ -86,7 +86,7 @@ public void activityResultListenerCanBeRemovedFromListener() {
8686

8787
// Set up the environment to get the required internal data
8888
FlutterEngineConnectionRegistry registry =
89-
new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader);
89+
new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader, null);
9090
FakeActivityAwareFlutterPlugin fakePlugin = new FakeActivityAwareFlutterPlugin();
9191
registry.add(fakePlugin);
9292
registry.attachToActivity(appComponent, lifecycle);
@@ -129,7 +129,7 @@ public void softwareRendering() {
129129

130130
// Test attachToActivity with an Activity that has no Intent.
131131
FlutterEngineConnectionRegistry registry =
132-
new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader);
132+
new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader, null);
133133
registry.attachToActivity(appComponent, mock(Lifecycle.class));
134134
verify(platformViewsController).setSoftwareRendering(false);
135135

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

+33
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@
2525
import io.flutter.FlutterInjector;
2626
import io.flutter.embedding.engine.FlutterEngine;
2727
import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener;
28+
import io.flutter.embedding.engine.FlutterEngineGroup;
2829
import io.flutter.embedding.engine.FlutterJNI;
2930
import io.flutter.embedding.engine.loader.FlutterLoader;
31+
import io.flutter.embedding.engine.plugins.FlutterPlugin;
32+
import io.flutter.embedding.engine.plugins.PluginRegistry;
3033
import io.flutter.plugin.platform.PlatformViewsController;
3134
import io.flutter.plugins.GeneratedPluginRegistrant;
3235
import java.util.List;
@@ -323,4 +326,34 @@ public void itComesWithARunningDartExecutorIfJNIIsAlreadyAttached() throws NameN
323326

324327
assertTrue(engineUnderTest.getDartExecutor().isExecutingDart());
325328
}
329+
330+
@Test
331+
public void passesEngineGroupToPlugins() throws NameNotFoundException {
332+
Context packageContext = mock(Context.class);
333+
334+
when(mockContext.createPackageContext(any(), anyInt())).thenReturn(packageContext);
335+
when(flutterJNI.isAttached()).thenReturn(true);
336+
337+
FlutterEngineGroup mockGroup = mock(FlutterEngineGroup.class);
338+
339+
FlutterEngine engineUnderTest =
340+
new FlutterEngine(
341+
mockContext,
342+
mock(FlutterLoader.class),
343+
flutterJNI,
344+
new PlatformViewsController(),
345+
/*dartVmArgs=*/ new String[] {},
346+
/*automaticallyRegisterPlugins=*/ false,
347+
/*waitForRestorationData=*/ false,
348+
mockGroup);
349+
350+
PluginRegistry registry = engineUnderTest.getPlugins();
351+
FlutterPlugin mockPlugin = mock(FlutterPlugin.class);
352+
ArgumentCaptor<FlutterPlugin.FlutterPluginBinding> pluginBindingCaptor =
353+
ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class);
354+
registry.add(mockPlugin);
355+
verify(mockPlugin).onAttachedToEngine(pluginBindingCaptor.capture());
356+
assertNotNull(pluginBindingCaptor.getValue());
357+
assertEquals(mockGroup, pluginBindingCaptor.getValue().getEngineGroup());
358+
}
326359
}

0 commit comments

Comments
 (0)