Create FlutterActivity/FlutterFragment using light weight engine with FlutterEngineGroup (flutter/engine#36963)

This commit is contained in:
Nayuta403
2022-12-08 01:17:03 +08:00
committed by GitHub
parent 960af0a350
commit c2290f0a8f
15 changed files with 925 additions and 4 deletions

View File

@@ -2314,6 +2314,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/Flutte
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineCache.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroupCache.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterOverlaySurface.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java

View File

@@ -203,6 +203,7 @@ android_java_sources = [
"io/flutter/embedding/engine/FlutterEngineCache.java",
"io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java",
"io/flutter/embedding/engine/FlutterEngineGroup.java",
"io/flutter/embedding/engine/FlutterEngineGroupCache.java",
"io/flutter/embedding/engine/FlutterJNI.java",
"io/flutter/embedding/engine/FlutterOverlaySurface.java",
"io/flutter/embedding/engine/FlutterShellArgs.java",

View File

@@ -10,7 +10,9 @@ import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_DART_ENTRYPOINT;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_BACKGROUND_MODE;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_GROUP_ID;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DART_ENTRYPOINT;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DESTROY_ENGINE_WITH_ACTIVITY;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_ENABLE_STATE_RESTORATION;
@@ -276,7 +278,7 @@ public class FlutterActivity extends Activity
}
/**
* The initial route that a Flutter app will render in this {@link FlutterFragment}, defaults to
* The initial route that a Flutter app will render in this {@link FlutterActivity}, defaults to
* "/".
*
* @param initialRoute The route.
@@ -448,6 +450,149 @@ public class FlutterActivity extends Activity
}
}
/**
* Creates a {@link NewEngineInGroupIntentBuilder}, which can be used to configure an {@link
* Intent} to launch a {@code FlutterActivity} by internally creating a FlutterEngine from an
* existing {@link io.flutter.embedding.engine.FlutterEngineGroup} cached in a specified {@link
* io.flutter.embedding.engine.FlutterEngineGroupCache}.
*
* <pre>{@code
* // Create a FlutterEngineGroup, such as in the onCreate method of the Application.
* FlutterEngineGroup engineGroup = new FlutterEngineGroup(this);
* FlutterEngineGroupCache.getInstance().put("my_cached_engine_group_id", engineGroup);
*
* // Start a FlutterActivity with the FlutterEngineGroup by creating an intent with withNewEngineInGroup
* Intent intent = FlutterActivity.withNewEngineInGroup("my_cached_engine_group_id")
* .dartEntrypoint("custom_entrypoint")
* .initialRoute("/custom/route")
* .backgroundMode(BackgroundMode.transparent)
* .build(context);
* startActivity(intent);
* }</pre>
*
* @param engineGroupId A cached engine group ID.
* @return The builder.
*/
public static NewEngineInGroupIntentBuilder withNewEngineInGroup(@NonNull String engineGroupId) {
return new NewEngineInGroupIntentBuilder(FlutterActivity.class, engineGroupId);
}
/**
* Builder to create an {@code Intent} that launches a {@code FlutterActivity} with a new {@link
* FlutterEngine} created by FlutterEngineGroup#createAndRunEngine.
*/
public static class NewEngineInGroupIntentBuilder {
private final Class<? extends FlutterActivity> activityClass;
private final String cachedEngineGroupId;
private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT;
private String initialRoute = DEFAULT_INITIAL_ROUTE;
private String backgroundMode = DEFAULT_BACKGROUND_MODE;
/**
* Constructor that allows this {@code NewEngineInGroupIntentBuilder} to be used by subclasses
* of {@code FlutterActivity}.
*
* <p>Subclasses of {@code FlutterActivity} should provide their own static version of {@link
* #withNewEngineInGroup}, which returns an instance of {@code NewEngineInGroupIntentBuilder}
* constructed with a {@code Class} reference to the {@code FlutterActivity} subclass, e.g.:
*
* <p>{@code return new NewEngineInGroupIntentBuilder(MyFlutterActivity.class,
* cacheedEngineGroupId); }
*
* <pre>{@code
* // Create a FlutterEngineGroup, such as in the onCreate method of the Application.
* FlutterEngineGroup engineGroup = new FlutterEngineGroup(this);
* FlutterEngineGroupCache.getInstance().put("my_cached_engine_group_id", engineGroup);
*
* // Create a NewEngineInGroupIntentBuilder that would build an intent to start my custom FlutterActivity subclass.
* FlutterActivity.NewEngineInGroupIntentBuilder intentBuilder =
* new FlutterActivity.NewEngineInGroupIntentBuilder(
* MyFlutterActivity.class,
* app.engineGroupId);
* intentBuilder.dartEntrypoint("main")
* .initialRoute("/custom/route")
* .backgroundMode(BackgroundMode.transparent);
* startActivity(intentBuilder.build(context));
* }</pre>
*
* @param activityClass A subclass of {@code FlutterActivity}.
* @param engineGroupId The engine group id.
*/
public NewEngineInGroupIntentBuilder(
@NonNull Class<? extends FlutterActivity> activityClass, @NonNull String engineGroupId) {
this.activityClass = activityClass;
this.cachedEngineGroupId = engineGroupId;
}
/**
* The Dart entrypoint that will be executed in the newly created FlutterEngine as soon as the
* Dart snapshot is loaded. Default to "main".
*
* @param dartEntrypoint The dart entrypoint's name
* @return The engine group intent builder
*/
@NonNull
public NewEngineInGroupIntentBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
this.dartEntrypoint = dartEntrypoint;
return this;
}
/**
* The initial route that a Flutter app will render in this {@link FlutterActivity}, defaults to
* "/".
*
* @param initialRoute The route.
* @return The engine group intent builder.
*/
@NonNull
public NewEngineInGroupIntentBuilder initialRoute(@NonNull String initialRoute) {
this.initialRoute = initialRoute;
return this;
}
/**
* The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or
* {@link BackgroundMode#transparent}.
*
* <p>The default background mode is {@link BackgroundMode#opaque}.
*
* <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
* {@link FlutterView} of this {@code FlutterActivity} to be configured with a {@link
* FlutterTextureView} to support transparency. This choice has a non-trivial performance
* impact. A transparent background should only be used if it is necessary for the app design
* being implemented.
*
* <p>A {@code FlutterActivity} that is configured with a background mode of {@link
* BackgroundMode#transparent} must have a theme applied to it that includes the following
* property: {@code <item name="android:windowIsTranslucent">true</item>}.
*
* @param backgroundMode The background mode.
* @return The engine group intent builder.
*/
@NonNull
public NewEngineInGroupIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
this.backgroundMode = backgroundMode.name();
return this;
}
/**
* Creates and returns an {@link Intent} that will launch a {@code FlutterActivity} with the
* desired configuration.
*
* @param context The context. e.g. An Activity.
* @return The intent.
*/
@NonNull
public Intent build(@NonNull Context context) {
return new Intent(context, activityClass)
.putExtra(EXTRA_DART_ENTRYPOINT, dartEntrypoint)
.putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
.putExtra(EXTRA_CACHED_ENGINE_GROUP_ID, cachedEngineGroupId)
.putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
.putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
}
}
// Delegate that runs all lifecycle and OS hook logic that is common between
// FlutterActivity and FlutterFragment. See the FlutterActivityAndFragmentDelegate
// implementation for details about why it exists.
@@ -866,6 +1011,17 @@ public class FlutterActivity extends Activity
return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID);
}
/**
* Returns the ID of a statically cached {@link io.flutter.embedding.engine.FlutterEngineGroup} to
* use within this {@code FlutterActivity}, or {@code null} if this {@code FlutterActivity} does
* not want to use a cached {@link io.flutter.embedding.engine.FlutterEngineGroup}.
*/
@Override
@Nullable
public String getCachedEngineGroupId() {
return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_GROUP_ID);
}
/**
* Returns false if the {@link io.flutter.embedding.engine.FlutterEngine} backing this {@code
* FlutterActivity} should outlive this {@code FlutterActivity}, or true to be destroyed when the
@@ -892,14 +1048,26 @@ public class FlutterActivity extends Activity
/**
* The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded.
*
* <p>This preference can be controlled by setting a {@code <meta-data>} called {@link
* FlutterActivityLaunchConfigs#DART_ENTRYPOINT_META_DATA_KEY} within the Android manifest
* definition for this {@code FlutterActivity}.
* <p>This preference can be controlled with 2 methods:
*
* <ol>
* <li>Pass a boolean as {@link FlutterActivityLaunchConfigs#EXTRA_DART_ENTRYPOINT} with the
* launching {@code Intent}, or
* <li>Set a {@code <meta-data>} called {@link
* FlutterActivityLaunchConfigs#DART_ENTRYPOINT_META_DATA_KEY} within the Android manifest
* definition for this {@code FlutterActivity}
* </ol>
*
* If both preferences are set, the {@code Intent} preference takes priority.
*
* <p>Subclasses may override this method to directly control the Dart entrypoint.
*/
@NonNull
public String getDartEntrypointFunctionName() {
if (getIntent().hasExtra(EXTRA_DART_ENTRYPOINT)) {
return getIntent().getStringExtra(EXTRA_DART_ENTRYPOINT);
}
try {
Bundle metaData = getMetaData();
String desiredDartEntrypoint =

View File

@@ -24,6 +24,8 @@ import io.flutter.FlutterInjector;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.FlutterEngineGroup;
import io.flutter.embedding.engine.FlutterEngineGroupCache;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
@@ -230,6 +232,10 @@ import java.util.List;
* <p>Second, the {@code host} is given an opportunity to provide a {@link
* io.flutter.embedding.engine.FlutterEngine} via {@link Host#provideFlutterEngine(Context)}.
*
* <p>Third, the {@code host} is asked if it would like to use a cached {@link
* io.flutter.embedding.engine.FlutterEngineGroup} to create a new {@link FlutterEngine} by {@link
* FlutterEngineGroup#createAndRunEngine}
*
* <p>If the {@code host} does not provide a {@link io.flutter.embedding.engine.FlutterEngine},
* then a new {@link FlutterEngine} is instantiated.
*/
@@ -258,6 +264,34 @@ import java.util.List;
return;
}
// Third, check if the host wants to use a cached FlutterEngineGroup
// and create new FlutterEngine using FlutterEngineGroup#createAndRunEngine
String cachedEngineGroupId = host.getCachedEngineGroupId();
if (cachedEngineGroupId != null) {
FlutterEngineGroup flutterEngineGroup =
FlutterEngineGroupCache.getInstance().get(cachedEngineGroupId);
if (flutterEngineGroup == null) {
throw new IllegalStateException(
"The requested cached FlutterEngineGroup did not exist in the FlutterEngineGroupCache: '"
+ cachedEngineGroupId
+ "'");
}
String appBundlePathOverride = host.getAppBundlePath();
if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
}
DartExecutor.DartEntrypoint dartEntrypoint =
new DartExecutor.DartEntrypoint(
appBundlePathOverride, host.getDartEntrypointFunctionName());
flutterEngine =
flutterEngineGroup.createAndRunEngine(
host.getContext(), dartEntrypoint, host.getInitialRoute());
isFlutterEngineFromHost = false;
return;
}
// Our host did not provide a custom FlutterEngine. Create a FlutterEngine to back our
// FlutterView.
Log.v(
@@ -915,6 +949,9 @@ import java.util.List;
@Nullable
String getCachedEngineId();
@Nullable
String getCachedEngineGroupId();
/**
* Returns true if the {@link io.flutter.embedding.engine.FlutterEngine} used in this delegate
* should be destroyed when the host/delegate are destroyed.

View File

@@ -20,10 +20,12 @@ public class FlutterActivityLaunchConfigs {
/* package */ static final String HANDLE_DEEPLINKING_META_DATA_KEY =
"flutter_deeplinking_enabled";
// Intent extra arguments.
/* package */ static final String EXTRA_DART_ENTRYPOINT = "dart_entrypoint";
/* package */ static final String EXTRA_INITIAL_ROUTE = "route";
/* package */ static final String EXTRA_BACKGROUND_MODE = "background_mode";
/* package */ static final String EXTRA_CACHED_ENGINE_ID = "cached_engine_id";
/* package */ static final String EXTRA_DART_ENTRYPOINT_ARGS = "dart_entrypoint_args";
/* package */ static final String EXTRA_CACHED_ENGINE_GROUP_ID = "cached_engine_group_id";
/* package */ static final String EXTRA_DESTROY_ENGINE_WITH_ACTIVITY =
"destroy_engine_with_activity";
/* package */ static final String EXTRA_ENABLE_STATE_RESTORATION = "enable_state_restoration";

View File

@@ -144,6 +144,9 @@ public class FlutterFragment extends Fragment
* FlutterFragment}.
*/
protected static final String ARG_CACHED_ENGINE_ID = "cached_engine_id";
protected static final String ARG_CACHED_ENGINE_GROUP_ID = "cached_engine_group_id";
/**
* True if the {@link io.flutter.embedding.engine.FlutterEngine} in the created {@code
* FlutterFragment} should be destroyed when the {@code FlutterFragment} is destroyed, false if
@@ -729,6 +732,259 @@ public class FlutterFragment extends Fragment
}
}
/**
* Returns a {@link NewEngineInGroupFragmentBuilder} to create a {@code FlutterFragment} with a
* cached {@link io.flutter.embedding.engine.FlutterEngineGroup} in {@link
* io.flutter.embedding.engine.FlutterEngineGroupCache}.
*
* <p>An {@code IllegalStateException} will be thrown during the lifecycle of the {@code
* FlutterFragment} if a cached {@link io.flutter.embedding.engine.FlutterEngineGroup} is
* requested but does not exist in the {@link
* io.flutter.embedding.engine.FlutterEngineGroupCache}.
*/
@NonNull
public static NewEngineInGroupFragmentBuilder withNewEngineInGroup(
@NonNull String engineGroupId) {
return new NewEngineInGroupFragmentBuilder(engineGroupId);
}
/**
* Builder that creates a new {@code FlutterFragment} that uses a cached {@link
* io.flutter.embedding.engine.FlutterEngineGroup} to create a new {@link
* io.flutter.embedding.engine.FlutterEngine} with {@code arguments} that correspond to the values
* set on this {@code Builder}.
*
* <p>Subclasses of {@code FlutterFragment} that do not introduce any new arguments can use this
* {@code Builder} to construct instances of the subclass without subclassing this {@code
* Builder}. {@code MyFlutterFragment f = new
* FlutterFragment.NewEngineInGroupFragmentBuilder(MyFlutterFragment.class, engineGroupId)
* .someProperty(...) .someOtherProperty(...) .build<MyFlutterFragment>(); }
*
* <p>Subclasses of {@code FlutterFragment} that introduce new arguments should subclass this
* {@code NewEngineInGroupFragmentBuilder} to add the new properties:
*
* <ol>
* <li>Ensure the {@code FlutterFragment} subclass has a no-arg constructor.
* <li>Subclass this {@code NewEngineInGroupFragmentBuilder}.
* <li>Override the new {@code NewEngineInGroupFragmentBuilder}'s no-arg constructor and invoke
* the super constructor to set the {@code FlutterFragment} subclass: {@code public
* MyBuilder() { super(MyFlutterFragment.class); } }
* <li>Add appropriate property methods for the new properties.
* <li>Override {@link NewEngineInGroupFragmentBuilder#createArgs()}, call through to the super
* method, then add the new properties as arguments in the {@link Bundle}.
* </ol>
*
* Once a {@code NewEngineInGroupFragmentBuilder} subclass is defined, the {@code FlutterFragment}
* subclass can be instantiated as follows. {@code MyFlutterFragment f = new MyBuilder()
* .someExistingProperty(...) .someNewProperty(...) .build<MyFlutterFragment>(); }
*/
public static class NewEngineInGroupFragmentBuilder {
private final Class<? extends FlutterFragment> fragmentClass;
private final String cachedEngineGroupId;
private @NonNull String dartEntrypoint = "main";
private @NonNull String initialRoute = "/";
private @NonNull boolean handleDeeplinking = false;
private @NonNull RenderMode renderMode = RenderMode.surface;
private @NonNull TransparencyMode transparencyMode = TransparencyMode.transparent;
private boolean shouldAttachEngineToActivity = true;
private boolean shouldAutomaticallyHandleOnBackPressed = false;
private boolean shouldDelayFirstAndroidViewDraw = false;
public NewEngineInGroupFragmentBuilder(@NonNull String engineGroupId) {
this(FlutterFragment.class, engineGroupId);
}
public NewEngineInGroupFragmentBuilder(
@NonNull Class<? extends FlutterFragment> fragmentClass, @NonNull String engineGroupId) {
this.fragmentClass = fragmentClass;
this.cachedEngineGroupId = engineGroupId;
}
/** The name of the initial Dart method to invoke, defaults to "main". */
@NonNull
public NewEngineInGroupFragmentBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
this.dartEntrypoint = dartEntrypoint;
return this;
}
/**
* The initial route that a Flutter app will render in this {@link FlutterFragment}, defaults to
* "/".
*/
@NonNull
public NewEngineInGroupFragmentBuilder initialRoute(@NonNull String initialRoute) {
this.initialRoute = initialRoute;
return this;
}
/**
* Whether to handle the deeplinking from the {@code Intent} automatically if the {@code
* getInitialRoute} returns null.
*/
@NonNull
public NewEngineInGroupFragmentBuilder handleDeeplinking(@NonNull boolean handleDeeplinking) {
this.handleDeeplinking = handleDeeplinking;
return this;
}
/**
* Render Flutter either as a {@link RenderMode#surface} or a {@link RenderMode#texture}. You
* should use {@code surface} unless you have a specific reason to use {@code texture}. {@code
* texture} comes with a significant performance impact, but {@code texture} can be displayed
* beneath other Android {@code View}s and animated, whereas {@code surface} cannot.
*/
@NonNull
public NewEngineInGroupFragmentBuilder renderMode(@NonNull RenderMode renderMode) {
this.renderMode = renderMode;
return this;
}
/**
* Support a {@link TransparencyMode#transparent} background within {@link
* io.flutter.embedding.android.FlutterView}, or force an {@link TransparencyMode#opaque}
* background.
*
* <p>See {@link TransparencyMode} for implications of this selection.
*/
@NonNull
public NewEngineInGroupFragmentBuilder transparencyMode(
@NonNull TransparencyMode transparencyMode) {
this.transparencyMode = transparencyMode;
return this;
}
/**
* Whether or not this {@code FlutterFragment} should automatically attach its {@code Activity}
* as a control surface for its {@link io.flutter.embedding.engine.FlutterEngine}.
*
* <p>Control surfaces are used to provide Android resources and lifecycle events to plugins
* that are attached to the {@link io.flutter.embedding.engine.FlutterEngine}. If {@code
* shouldAttachEngineToActivity} is true then this {@code FlutterFragment} will connect its
* {@link io.flutter.embedding.engine.FlutterEngine} to the surrounding {@code Activity}, along
* with any plugins that are registered with that {@link FlutterEngine}. This allows plugins to
* access the {@code Activity}, as well as receive {@code Activity}-specific calls, e.g., {@link
* android.app.Activity#onNewIntent(Intent)}. If {@code shouldAttachEngineToActivity} is false,
* then this {@code FlutterFragment} will not automatically manage the connection between its
* {@link io.flutter.embedding.engine.FlutterEngine} and the surrounding {@code Activity}. The
* {@code Activity} will need to be manually connected to this {@code FlutterFragment}'s {@link
* io.flutter.embedding.engine.FlutterEngine} by the app developer. See {@link
* FlutterEngine#getActivityControlSurface()}.
*
* <p>One reason that a developer might choose to manually manage the relationship between the
* {@code Activity} and {@link io.flutter.embedding.engine.FlutterEngine} is if the developer
* wants to move the {@link FlutterEngine} somewhere else. For example, a developer might want
* the {@link io.flutter.embedding.engine.FlutterEngine} to outlive the surrounding {@code
* Activity} so that it can be used later in a different {@code Activity}. To accomplish this,
* the {@link io.flutter.embedding.engine.FlutterEngine} will need to be disconnected from the
* surrounding {@code Activity} at an unusual time, preventing this {@code FlutterFragment} from
* correctly managing the relationship between the {@link
* io.flutter.embedding.engine.FlutterEngine} and the surrounding {@code Activity}.
*
* <p>Another reason that a developer might choose to manually manage the relationship between
* the {@code Activity} and {@link io.flutter.embedding.engine.FlutterEngine} is if the
* developer wants to prevent, or explicitly control when the {@link
* io.flutter.embedding.engine.FlutterEngine}'s plugins have access to the surrounding {@code
* Activity}. For example, imagine that this {@code FlutterFragment} only takes up part of the
* screen and the app developer wants to ensure that none of the Flutter plugins are able to
* manipulate the surrounding {@code Activity}. In this case, the developer would not want the
* {@link io.flutter.embedding.engine.FlutterEngine} to have access to the {@code Activity},
* which can be accomplished by setting {@code shouldAttachEngineToActivity} to {@code false}.
*/
@NonNull
public NewEngineInGroupFragmentBuilder shouldAttachEngineToActivity(
boolean shouldAttachEngineToActivity) {
this.shouldAttachEngineToActivity = shouldAttachEngineToActivity;
return this;
}
/**
* Whether or not this {@code FlutterFragment} should automatically receive {@link
* #onBackPressed()} events, rather than requiring an explicit activity call through. Disabled
* by default.
*
* <p>When enabled, the activity will automatically dispatch back-press events to the fragment's
* {@link OnBackPressedCallback}, instead of requiring the activity to manually call {@link
* #onBackPressed()} in client code. If enabled, do <b>not</b> invoke {@link #onBackPressed()}
* manually.
*
* <p>This behavior relies on the implementation of {@link #popSystemNavigator()}. It's not
* recommended to override that method when enabling this attribute, but if you do, you should
* always fall back to calling {@code super.popSystemNavigator()} when not relying on custom
* behavior.
*/
@NonNull
public NewEngineInGroupFragmentBuilder shouldAutomaticallyHandleOnBackPressed(
boolean shouldAutomaticallyHandleOnBackPressed) {
this.shouldAutomaticallyHandleOnBackPressed = shouldAutomaticallyHandleOnBackPressed;
return this;
}
/**
* Whether to delay the Android drawing pass till after the Flutter UI has been displayed.
*
* <p>See {#link FlutterActivityAndFragmentDelegate#onCreateView} for more details.
*/
@NonNull
public NewEngineInGroupFragmentBuilder shouldDelayFirstAndroidViewDraw(
@NonNull boolean shouldDelayFirstAndroidViewDraw) {
this.shouldDelayFirstAndroidViewDraw = shouldDelayFirstAndroidViewDraw;
return this;
}
/**
* Creates a {@link Bundle} of arguments that are assigned to the new {@code FlutterFragment}.
*
* <p>Subclasses should override this method to add new properties to the {@link Bundle}.
* Subclasses must call through to the super method to collect all existing property values.
*/
@NonNull
protected Bundle createArgs() {
Bundle args = new Bundle();
args.putString(ARG_CACHED_ENGINE_GROUP_ID, cachedEngineGroupId);
args.putString(ARG_DART_ENTRYPOINT, dartEntrypoint);
args.putString(ARG_INITIAL_ROUTE, initialRoute);
args.putBoolean(ARG_HANDLE_DEEPLINKING, handleDeeplinking);
args.putString(
ARG_FLUTTERVIEW_RENDER_MODE,
renderMode != null ? renderMode.name() : RenderMode.surface.name());
args.putString(
ARG_FLUTTERVIEW_TRANSPARENCY_MODE,
transparencyMode != null ? transparencyMode.name() : TransparencyMode.transparent.name());
args.putBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY, shouldAttachEngineToActivity);
args.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, true);
args.putBoolean(
ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, shouldAutomaticallyHandleOnBackPressed);
args.putBoolean(ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW, shouldDelayFirstAndroidViewDraw);
return args;
}
/**
* Constructs a new {@code FlutterFragment} (or a subclass) that is configured based on
* properties set on this {@code Builder}.
*/
@NonNull
public <T extends FlutterFragment> T build() {
try {
@SuppressWarnings("unchecked")
T frag = (T) fragmentClass.getDeclaredConstructor().newInstance();
if (frag == null) {
throw new RuntimeException(
"The FlutterFragment subclass sent in the constructor ("
+ fragmentClass.getCanonicalName()
+ ") does not match the expected return type.");
}
Bundle args = createArgs();
frag.setArguments(args);
return frag;
} catch (Exception e) {
throw new RuntimeException(
"Could not instantiate FlutterFragment subclass (" + fragmentClass.getName() + ")", e);
}
}
}
// Delegate that runs all lifecycle and OS hook logic that is common between
// FlutterActivity and FlutterFragment. See the FlutterActivityAndFragmentDelegate
// implementation for details about why it exists.
@@ -1018,6 +1274,17 @@ public class FlutterFragment extends Fragment
return getArguments().getString(ARG_CACHED_ENGINE_ID, null);
}
/**
* Returns the ID of a statically cached {@link io.flutter.embedding.engine.FlutterEngineGroup} to
* use within this {@code FlutterFragment}, or {@code null} if this {@code FlutterFragment} does
* not want to use a cached {@link io.flutter.embedding.engine.FlutterEngineGroup}.
*/
@Override
@Nullable
public String getCachedEngineGroupId() {
return getArguments().getString(ARG_CACHED_ENGINE_GROUP_ID, null);
}
/**
* Returns true a {@code FlutterEngine} was explicitly created and injected into the {@code
* FlutterFragment} rather than one that was created implicitly in the {@code FlutterFragment}.

View File

@@ -10,7 +10,9 @@ import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_DART_ENTRYPOINT;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_BACKGROUND_MODE;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_GROUP_ID;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DART_ENTRYPOINT;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DESTROY_ENGINE_WITH_ACTIVITY;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_INITIAL_ROUTE;
@@ -266,6 +268,112 @@ public class FlutterFragmentActivity extends FragmentActivity
}
}
/**
* Creates a {@link NewEngineInGroupIntentBuilder}, which can be used to configure an {@link
* Intent} to launch a {@code FlutterFragmentActivity} that internally uses an existing {@link
* io.flutter.embedding.engine.FlutterEngineGroup} that is cached in {@link
* io.flutter.embedding.engine.FlutterEngineGroupCache}.
*
* @param engineGroupId A cached engine group ID.
* @return The builder.
*/
public static NewEngineInGroupIntentBuilder withNewEngineInGroup(@NonNull String engineGroupId) {
return new NewEngineInGroupIntentBuilder(FlutterFragmentActivity.class, engineGroupId);
}
/**
* Builder to create an {@code Intent} that launches a {@code FlutterFragmentActivity} with a new
* {@link FlutterEngine} by FlutterEngineGroup#createAndRunEngine.
*/
public static class NewEngineInGroupIntentBuilder {
private final Class<? extends FlutterFragmentActivity> activityClass;
private final String cachedEngineGroupId;
private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT;
private String initialRoute = DEFAULT_INITIAL_ROUTE;
private String backgroundMode = DEFAULT_BACKGROUND_MODE;
/**
* Constructor that allows this {@code NewEngineInGroupIntentBuilder} to be used by subclasses
* of {@code FlutterActivity}.
*
* <p>Subclasses of {@code FlutterFragmentActivity} should provide their own static version of
* {@link #withNewEngineInGroup}, which returns an instance of {@code
* NewEngineInGroupIntentBuilder} constructed with a {@code Class} reference to the {@code
* FlutterFragmentActivity} subclass, e.g.:
*
* <p>{@code return new NewEngineInGroupIntentBuilder(FlutterFragmentActivity.class,
* cacheedEngineGroupId); }
*
* @param activityClass A subclass of {@code FlutterFragmentActivity}.
* @param engineGroupId The engine group id.
*/
public NewEngineInGroupIntentBuilder(
@NonNull Class<? extends FlutterFragmentActivity> activityClass,
@NonNull String engineGroupId) {
this.activityClass = activityClass;
this.cachedEngineGroupId = engineGroupId;
}
/**
* The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded, default to
* "main".
*
* @param dartEntrypoint The dart entrypoint's name
* @return The engine group intent builder
*/
@NonNull
public NewEngineInGroupIntentBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
this.dartEntrypoint = dartEntrypoint;
return this;
}
/**
* The initial route that a Flutter app will render in this {@code FlutterFragmentActivity},
* defaults to "/".
*/
@NonNull
public NewEngineInGroupIntentBuilder initialRoute(@NonNull String initialRoute) {
this.initialRoute = initialRoute;
return this;
}
/**
* The mode of {@code FlutterFragmentActivity}'s background, either {@link
* BackgroundMode#opaque} or {@link BackgroundMode#transparent}.
*
* <p>The default background mode is {@link BackgroundMode#opaque}.
*
* <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
* {@link FlutterView} of this {@code FlutterFragmentActivity} to be configured with a {@link
* FlutterTextureView} to support transparency. This choice has a non-trivial performance
* impact. A transparent background should only be used if it is necessary for the app design
* being implemented.
*
* <p>A {@code FlutterFragmentActivity} that is configured with a background mode of {@link
* BackgroundMode#transparent} must have a theme applied to it that includes the following
* property: {@code <item name="android:windowIsTranslucent">true</item>}.
*/
@NonNull
public NewEngineInGroupIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
this.backgroundMode = backgroundMode.name();
return this;
}
/**
* Creates and returns an {@link Intent} that will launch a {@code FlutterFragmentActivity} with
* the desired configuration.
*/
@NonNull
public Intent build(@NonNull Context context) {
return new Intent(context, activityClass)
.putExtra(EXTRA_DART_ENTRYPOINT, dartEntrypoint)
.putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
.putExtra(EXTRA_CACHED_ENGINE_GROUP_ID, cachedEngineGroupId)
.putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
.putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
}
}
@Nullable private FlutterFragment flutterFragment;
@Override
@@ -481,6 +589,9 @@ public class FlutterFragmentActivity extends FragmentActivity
Log.v(
TAG,
"Creating FlutterFragment with new engine:\n"
+ "Cached engine group ID: "
+ getCachedEngineGroupId()
+ "\n"
+ "Background transparency mode: "
+ backgroundMode
+ "\n"
@@ -499,6 +610,19 @@ public class FlutterFragmentActivity extends FragmentActivity
+ "Will attach FlutterEngine to Activity: "
+ shouldAttachEngineToActivity());
if (getCachedEngineGroupId() != null) {
return flutterFragment
.withNewEngineInGroup(getCachedEngineGroupId())
.dartEntrypoint(getDartEntrypointFunctionName())
.initialRoute(getInitialRoute())
.handleDeeplinking(shouldHandleDeeplinking())
.renderMode(renderMode)
.transparencyMode(transparencyMode)
.shouldAttachEngineToActivity(shouldAttachEngineToActivity())
.shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw)
.build();
}
return FlutterFragment.withNewEngine()
.dartEntrypoint(getDartEntrypointFunctionName())
.dartLibraryUri(getDartEntrypointLibraryUri())
@@ -813,6 +937,11 @@ public class FlutterFragmentActivity extends FragmentActivity
return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID);
}
@Nullable
protected String getCachedEngineGroupId() {
return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_GROUP_ID);
}
/**
* The desired window background mode of this {@code Activity}, which defaults to {@link
* BackgroundMode#opaque}.

View File

@@ -0,0 +1,100 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.embedding.engine;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.HashMap;
import java.util.Map;
/**
* Static singleton cache that holds {@link io.flutter.embedding.engine.FlutterEngineGroup}
* instances identified by {@code String}s.
*
* <p>The ID of a given {@link io.flutter.embedding.engine.FlutterEngineGroup} can be whatever
* {@code String} is desired.
*
* <p>{@link io.flutter.embedding.android.FlutterActivity} and {@link
* io.flutter.embedding.android.FlutterFragment} use the {@code FlutterEngineGroupCache} singleton
* internally when instructed to use a cached {@link io.flutter.embedding.engine.FlutterEngineGroup}
* based on a given ID. See {@link
* io.flutter.embedding.android.FlutterActivity.NewEngineInGroupIntentBuilder} and {@link
* io.flutter.embedding.android.FlutterFragment#withNewEngineInGroup(String)} for related APIs.
*/
public class FlutterEngineGroupCache {
private static volatile FlutterEngineGroupCache instance;
/**
* Returns the static singleton instance of {@code FlutterEngineGroupCache}.
*
* <p>Creates a new instance if one does not yet exist.
*/
@NonNull
public static FlutterEngineGroupCache getInstance() {
if (instance == null) {
synchronized (FlutterEngineGroupCache.class) {
if (instance == null) {
instance = new FlutterEngineGroupCache();
}
}
}
return instance;
}
private final Map<String, FlutterEngineGroup> cachedEngineGroups = new HashMap<>();
@VisibleForTesting
/* package */ FlutterEngineGroupCache() {}
/**
* Returns {@code true} if a {@link io.flutter.embedding.engine.FlutterEngineGroup} in this cache
* is associated with the given {@code engineGroupId}.
*/
public boolean contains(@NonNull String engineGroupId) {
return cachedEngineGroups.containsKey(engineGroupId);
}
/**
* Returns the {@link io.flutter.embedding.engine.FlutterEngineGroup} in this cache that is
* associated with the given {@code engineGroupId}, or {@code null} is no such {@link
* io.flutter.embedding.engine.FlutterEngineGroup} exists.
*/
@Nullable
public FlutterEngineGroup get(@NonNull String engineGroupId) {
return cachedEngineGroups.get(engineGroupId);
}
/**
* Places the given {@link io.flutter.embedding.engine.FlutterEngineGroup} in this cache and
* associates it with the given {@code engineGroupId}.
*
* <p>If a {@link io.flutter.embedding.engine.FlutterEngineGroup} is null, that {@link
* io.flutter.embedding.engine.FlutterEngineGroup} is removed from this cache.
*/
public void put(@NonNull String engineGroupId, @Nullable FlutterEngineGroup engineGroup) {
if (engineGroup != null) {
cachedEngineGroups.put(engineGroupId, engineGroup);
} else {
cachedEngineGroups.remove(engineGroupId);
}
}
/**
* Removes any {@link io.flutter.embedding.engine.FlutterEngineGroup} that is currently in the
* cache that is identified by the given {@code engineGroupId}.
*/
public void remove(@NonNull String engineGroupId) {
put(engineGroupId, null);
}
/**
* Removes all {@link io.flutter.embedding.engine.FlutterEngineGroup}'s that are currently in the
* cache.
*/
public void clear() {
cachedEngineGroups.clear();
}
}

View File

@@ -30,6 +30,8 @@ import io.flutter.FlutterInjector;
import io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.Host;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.FlutterEngineGroup;
import io.flutter.embedding.engine.FlutterEngineGroupCache;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.loader.FlutterLoader;
@@ -235,6 +237,57 @@ public class FlutterActivityAndFragmentDelegateTest {
// Expect IllegalStateException.
}
@Test
public void itUsesNewEngineInGroupWhenProvided() {
// ---- Test setup ----
FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
when(mockFlutterLoader.findAppBundlePath()).thenReturn("default_flutter_assets/path");
FlutterInjector.setInstance(
new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
FlutterEngineGroup flutterEngineGroup = mock(FlutterEngineGroup.class);
FlutterEngineGroupCache.getInstance().put("my_flutter_engine_group", flutterEngineGroup);
// Adjust fake host to request cached engine group.
when(mockHost.getCachedEngineGroupId()).thenReturn("my_flutter_engine_group");
when(mockHost.provideFlutterEngine(any(Context.class))).thenReturn(null);
when(mockHost.shouldAttachEngineToActivity()).thenReturn(false);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
// --- Execute the behavior under test ---
// The FlutterEngine is obtained in onAttach().
delegate.onAttach(ctx);
// If the engine in FlutterEngineGroup is being used, it should have sent a resumed lifecycle
// event.
// Note: "/fake/path" and "main" come from `setUp()`.
DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint("/fake/path", "main");
verify(flutterEngineGroup, times(1))
.createAndRunEngine(mockHost.getContext(), entrypoint, mockHost.getInitialRoute());
}
@Test(expected = IllegalStateException.class)
public void itThrowsExceptionIfNewEngineInGroupNotExist() {
// ---- Test setup ----
FlutterEngineGroupCache.getInstance().clear();
// Adjust fake host to request cached engine group that does not exist.
when(mockHost.getCachedEngineGroupId()).thenReturn("my_flutter_engine_group");
when(mockHost.getCachedEngineId()).thenReturn(null);
when(mockHost.provideFlutterEngine(any(Context.class))).thenReturn(null);
when(mockHost.shouldAttachEngineToActivity()).thenReturn(false);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
// --- Execute the behavior under test ---
// The FlutterEngine existence is verified in onAttach()
delegate.onAttach(ctx);
// Expect IllegalStateException.
}
@Test
public void itGivesHostAnOpportunityToConfigureFlutterEngine() {
// ---- Test setup ----

View File

@@ -192,6 +192,31 @@ public class FlutterActivityTest {
assertEquals(TransparencyMode.transparent, flutterActivity.getTransparencyMode());
}
@Test
public void itCreatesNewEngineInGroupIntentWithRequestedSettings() {
Intent intent =
FlutterActivity.withNewEngineInGroup("my_cached_engine_group")
.dartEntrypoint("custom_entrypoint")
.initialRoute("/custom/route")
.backgroundMode(BackgroundMode.transparent)
.build(ctx);
ActivityController<FlutterActivity> activityController =
Robolectric.buildActivity(FlutterActivity.class, intent);
FlutterActivity flutterActivity = activityController.get();
flutterActivity.setDelegate(new FlutterActivityAndFragmentDelegate(flutterActivity));
assertEquals("my_cached_engine_group", flutterActivity.getCachedEngineGroupId());
assertEquals("custom_entrypoint", flutterActivity.getDartEntrypointFunctionName());
assertEquals("/custom/route", flutterActivity.getInitialRoute());
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray());
assertTrue(flutterActivity.shouldAttachEngineToActivity());
assertTrue(flutterActivity.shouldDestroyEngineWithHost());
assertNull(flutterActivity.getCachedEngineId());
assertEquals(BackgroundMode.transparent, flutterActivity.getBackgroundMode());
assertEquals(RenderMode.texture, flutterActivity.getRenderMode());
assertEquals(TransparencyMode.transparent, flutterActivity.getTransparencyMode());
}
@Test
public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1()
throws PackageManager.NameNotFoundException {

View File

@@ -292,6 +292,12 @@ public class FlutterAndroidComponentTest {
return "my_flutter_engine";
}
@Nullable
@Override
public String getCachedEngineGroupId() {
return "my_flutter_engine_group";
}
@Override
public boolean shouldDestroyEngineWithHost() {
return shouldDestroyEngineWithHost;

View File

@@ -422,5 +422,11 @@ public class FlutterFragmentActivityTest {
return new CachedEngineIntentBuilder(
FlutterFragmentActivityWithIntentBuilders.class, cachedEngineId);
}
public static NewEngineInGroupIntentBuilder withNewEngineInGroup(
@NonNull String engineGroupId) {
return new NewEngineInGroupIntentBuilder(
FlutterFragmentActivityWithIntentBuilders.class, engineGroupId);
}
}
}

View File

@@ -102,6 +102,35 @@ public class FlutterFragmentTest {
assertEquals(TransparencyMode.opaque, fragment.getTransparencyMode());
}
@Test
public void itCreatesNewEngineInGroupFragmentWithRequestedSettings() {
FlutterFragment fragment =
FlutterFragment.withNewEngineInGroup("my_cached_engine_group")
.dartEntrypoint("custom_entrypoint")
.initialRoute("/custom/route")
.shouldAttachEngineToActivity(false)
.handleDeeplinking(true)
.renderMode(RenderMode.texture)
.transparencyMode(TransparencyMode.opaque)
.build();
TestDelegateFactory delegateFactory =
new TestDelegateFactory(new FlutterActivityAndFragmentDelegate(fragment));
fragment.setDelegateFactory(delegateFactory);
assertEquals("my_cached_engine_group", fragment.getCachedEngineGroupId());
assertEquals("custom_entrypoint", fragment.getDartEntrypointFunctionName());
assertEquals("/custom/route", fragment.getInitialRoute());
assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray());
assertFalse(fragment.shouldAttachEngineToActivity());
assertTrue(fragment.shouldHandleDeeplinking());
assertNull(fragment.getCachedEngineId());
assertTrue(fragment.shouldDestroyEngineWithHost());
assertEquals(RenderMode.texture, fragment.getRenderMode());
assertEquals(TransparencyMode.opaque, fragment.getTransparencyMode());
}
@Test
public void itCreatesNewEngineFragmentThatDelaysFirstDrawWhenRequested() {
FlutterFragment fragment =

View File

@@ -0,0 +1,96 @@
package io.flutter.embedding.engine;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.loader.FlutterLoader;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class FlutterEngineGroupCacheTest {
private FlutterEngineGroup flutterEngineGroup;
@Before
public void setup() {
// Create a mocked FlutterEngineGroup that provided to run this test case
FlutterInjector.reset();
FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
when(mockFlutterLoader.findAppBundlePath()).thenReturn("default_flutter_assets/path");
FlutterInjector.setInstance(
new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
flutterEngineGroup = mock(FlutterEngineGroup.class);
}
@Test
public void itHoldsFlutterEngineGroups() {
// --- Test Setup ---
FlutterEngineGroupCache cache = new FlutterEngineGroupCache();
// --- Execute Test ---
cache.put("my_flutter_engine_group", flutterEngineGroup);
// --- Verify Results ---
assertEquals(flutterEngineGroup, cache.get("my_flutter_engine_group"));
}
@Test
public void itQueriesFlutterEngineGroupExistence() {
// --- Test Setup ---
FlutterEngineGroupCache cache = new FlutterEngineGroupCache();
// --- Execute Test ---
assertFalse(cache.contains("my_flutter_engine_group"));
cache.put("my_flutter_engine_group", flutterEngineGroup);
// --- Verify Results ---
assertTrue(cache.contains("my_flutter_engine_group"));
}
@Test
public void itRemovesFlutterEngineGroups() {
// --- Test Setup ---
FlutterEngineGroupCache cache = new FlutterEngineGroupCache();
// --- Execute Test ---
cache.put("my_flutter_engine_group", flutterEngineGroup);
cache.remove("my_flutter_engine_group");
// --- Verify Results ---
assertNull(cache.get("my_flutter_engine_group"));
}
@Test
public void itRemovesAllFlutterEngineGroups() {
// --- Test Setup ---
FlutterEngineGroup flutterEngineGroup1 = new FlutterEngineGroup(RuntimeEnvironment.application);
FlutterEngineGroup flutterEngineGroup2 = new FlutterEngineGroup(RuntimeEnvironment.application);
FlutterEngineGroupCache cache = new FlutterEngineGroupCache();
// --- Execute Test ---
cache.put("my_flutter_engine_group", flutterEngineGroup1);
cache.put("my_flutter_engine_group_2", flutterEngineGroup2);
// --- Verify Results ---
assertEquals(flutterEngineGroup1, cache.get("my_flutter_engine_group"));
assertEquals(flutterEngineGroup2, cache.get("my_flutter_engine_group_2"));
cache.clear();
// --- Verify Results ---
assertNull(cache.get("my_flutter_engine_group"));
assertNull(cache.get("my_flutter_engine_group_2"));
}
}

View File

@@ -106,6 +106,7 @@
<src file="../../../flutter/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/DeferredComponentManager.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroupCache.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRegistry.java" />
<src file="../../../flutter/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java" />