From 47a358c5e8fc4a6deb057f738ce1e3432eb9c98f Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Thu, 15 Dec 2022 13:50:38 -0800 Subject: [PATCH] Started using FlutterEngineGroups by default on Android (#37822) * Started using FlutterEngineGroups by default on Android and making them accessible to plugins. * added docstring * format --- .../embedding/android/FlutterActivity.java | 3 +- .../FlutterActivityAndFragmentDelegate.java | 22 ++++++++++--- .../embedding/android/FlutterFragment.java | 3 +- .../embedding/engine/FlutterEngine.java | 25 +++++++++++++- .../FlutterEngineConnectionRegistry.java | 6 ++-- .../embedding/engine/FlutterEngineGroup.java | 3 +- .../engine/plugins/FlutterPlugin.java | 22 ++++++++++++- .../plugins/shim/ShimPluginRegistry.java | 3 +- ...lutterActivityAndFragmentDelegateTest.java | 16 +++++++++ .../FlutterEngineConnectionRegistryTest.java | 6 ++-- .../embedding/engine/FlutterEngineTest.java | 33 +++++++++++++++++++ 11 files changed, 126 insertions(+), 16 deletions(-) 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); + ArgumentCaptorpluginBindingCaptor = + ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class); + registry.add(mockPlugin); + verify(mockPlugin).onAttachedToEngine(pluginBindingCaptor.capture()); + assertNotNull(pluginBindingCaptor.getValue()); + assertEquals(mockGroup, pluginBindingCaptor.getValue().getEngineGroup()); + } }