Create FlutterActivity/FlutterFragment using light weight engine with FlutterEngineGroup (flutter/engine#36963)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 ----
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user