未验证 提交 735255f3 编写于 作者: M Matt Carroll 提交者: GitHub

Android embedding refactor pr40 add static engine cache (#10481)

上级 b0f7c2b6
......@@ -481,7 +481,7 @@ deps = {
'packages': [
{
'package': 'flutter/android/robolectric_bundle',
'version': 'last_updated:2019-07-29T15:27:42-0700'
'version': 'last_updated:2019-08-02T16:01:27-0700'
}
],
'condition': 'download_android_deps',
......
......@@ -565,6 +565,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Splas
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/SplashScreenProvider.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineAndroidLifecycle.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineCache.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEnginePluginRegistry.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java
......
......@@ -139,6 +139,7 @@ action("flutter_shell_java") {
"io/flutter/embedding/android/SplashScreenProvider.java",
"io/flutter/embedding/engine/FlutterEngine.java",
"io/flutter/embedding/engine/FlutterEngineAndroidLifecycle.java",
"io/flutter/embedding/engine/FlutterEngineCache.java",
"io/flutter/embedding/engine/FlutterEnginePluginRegistry.java",
"io/flutter/embedding/engine/FlutterJNI.java",
"io/flutter/embedding/engine/FlutterShellArgs.java",
......@@ -330,6 +331,9 @@ action("robolectric_tests") {
"test/io/flutter/FlutterTestSuite.java",
"test/io/flutter/SmokeTest.java",
"test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java",
"test/io/flutter/embedding/android/FlutterActivityTest.java",
"test/io/flutter/embedding/android/FlutterFragmentTest.java",
"test/io/flutter/embedding/engine/FlutterEngineCacheTest.java",
"test/io/flutter/util/PreconditionsTest.java",
]
......@@ -351,6 +355,7 @@ action("robolectric_tests") {
"//third_party/robolectric/lib/common-1.1.1.jar",
"//third_party/robolectric/lib/common-java8-1.1.1.jar",
"//third_party/robolectric/lib/support-annotations-28.0.0.jar",
"//third_party/robolectric/lib/support-fragment-25.2.0.jar",
"//third_party/robolectric/lib/mockito-all-1.10.19.jar",
]
......
......@@ -47,11 +47,11 @@ import io.flutter.view.FlutterMain;
* route may be specified explicitly by passing the name of the route as a {@code String} in
* {@link #EXTRA_INITIAL_ROUTE}, e.g., "my/deep/link".
* <p>
* The Dart entrypoint and initial route can each be controlled using a {@link IntentBuilder}
* The Dart entrypoint and initial route can each be controlled using a {@link NewEngineIntentBuilder}
* via the following methods:
* <ul>
* <li>{@link IntentBuilder#dartEntrypoint}</li>
* <li>{@link IntentBuilder#initialRoute}</li>
* <li>{@link NewEngineIntentBuilder#dartEntrypoint}</li>
* <li>{@link NewEngineIntentBuilder#initialRoute}</li>
* </ul>
* <p>
* The app bundle path, Dart entrypoint, and initial route can also be controlled in a subclass of
......@@ -61,6 +61,37 @@ import io.flutter.view.FlutterMain;
* <li>{@link #getDartEntrypointFunctionName()}</li>
* <li>{@link #getInitialRoute()}</li>
* </ul>
* <p>
* {@code FlutterActivity} can be used with a cached {@link FlutterEngine} instead of creating a new
* one. Use {@link #withCachedEngine(String)} to build a {@code FlutterActivity} {@code Intent} that
* is configured to use an existing, cached {@link FlutterEngine}. {@link FlutterEngineCache} is the
* cache that is used to obtain a given cached {@link FlutterEngine}. An
* {@code IllegalStateException} will be thrown if a cached engine is requested but does not exist
* in the cache.
* <p>
* It is generally recommended to use a cached {@link FlutterEngine} to avoid a momentary delay
* when initializing a new {@link FlutterEngine}. The two exceptions to using a cached
* {@link FlutterEngine} are:
* <p>
* <ul>
* <li>When {@code FlutterActivity} is the first {@code Activity} displayed by the app, because
* pre-warming a {@link FlutterEngine} would have no impact in this situation.</li>
* <li>When you are unsure when/if you will need to display a Flutter experience.</li>
* </ul>
* <p>
* The following illustrates how to pre-warm and cache a {@link FlutterEngine}:
* <p>
* {@code
* // Create and pre-warm a FlutterEngine.
* FlutterEngine flutterEngine = new FlutterEngine(context);
* flutterEngine
* .getDartExecutor()
* .executeDartEntrypoint(DartEntrypoint.createDefault());
*
* // Cache the pre-warmed FlutterEngine in the FlutterEngineCache.
* FlutterEngineCache.getInstance().put("my_engine", flutterEngine);
* }
* <p>
* If Flutter is needed in a location that cannot use an {@code Activity}, consider using
* a {@link FlutterFragment}. Using a {@link FlutterFragment} requires forwarding some calls from
* an {@code Activity} to the {@link FlutterFragment}.
......@@ -149,6 +180,8 @@ public class FlutterActivity extends Activity
protected static final String EXTRA_DART_ENTRYPOINT = "dart_entrypoint";
protected static final String EXTRA_INITIAL_ROUTE = "initial_route";
protected static final String EXTRA_BACKGROUND_MODE = "background_mode";
protected static final String EXTRA_CACHED_ENGINE_ID = "cached_engine_id";
protected static final String EXTRA_DESTROY_ENGINE_WITH_ACTIVITY = "destroy_engine_with_activity";
// Default configuration.
protected static final String DEFAULT_DART_ENTRYPOINT = "main";
......@@ -161,42 +194,43 @@ public class FlutterActivity extends Activity
*/
@NonNull
public static Intent createDefaultIntent(@NonNull Context launchContext) {
return createBuilder().build(launchContext);
return withNewEngine().build(launchContext);
}
/**
* Creates an {@link IntentBuilder}, which can be used to configure an {@link Intent} to
* launch a {@code FlutterActivity}.
* Creates an {@link NewEngineIntentBuilder}, which can be used to configure an {@link Intent} to
* launch a {@code FlutterActivity} that internally creates a new {@link FlutterEngine} using
* the desired Dart entrypoint, initial route, etc.
*/
@NonNull
public static IntentBuilder createBuilder() {
return new IntentBuilder(FlutterActivity.class);
public static NewEngineIntentBuilder withNewEngine() {
return new NewEngineIntentBuilder(FlutterActivity.class);
}
/**
* Builder to create an {@code Intent} that launches a {@code FlutterActivity} with the
* desired configuration.
* Builder to create an {@code Intent} that launches a {@code FlutterActivity} with a new
* {@link FlutterEngine} and the desired configuration.
*/
public static class IntentBuilder {
public static class NewEngineIntentBuilder {
private final Class<? extends FlutterActivity> activityClass;
private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT;
private String initialRoute = DEFAULT_INITIAL_ROUTE;
private String backgroundMode = DEFAULT_BACKGROUND_MODE;
/**
* Constructor that allows this {@code IntentBuilder} to be used by subclasses of
* Constructor that allows this {@code NewEngineIntentBuilder} to be used by subclasses of
* {@code FlutterActivity}.
* <p>
* Subclasses of {@code FlutterActivity} should provide their own static version of
* {@link #createBuilder()}, which returns an instance of {@code IntentBuilder}
* {@link #withNewEngine()}, which returns an instance of {@code NewEngineIntentBuilder}
* constructed with a {@code Class} reference to the {@code FlutterActivity} subclass,
* e.g.:
* <p>
* {@code
* return new IntentBuilder(MyFlutterActivity.class);
* return new NewEngineIntentBuilder(MyFlutterActivity.class);
* }
*/
protected IntentBuilder(@NonNull Class<? extends FlutterActivity> activityClass) {
protected NewEngineIntentBuilder(@NonNull Class<? extends FlutterActivity> activityClass) {
this.activityClass = activityClass;
}
......@@ -204,7 +238,7 @@ public class FlutterActivity extends Activity
* The name of the initial Dart method to invoke, defaults to "main".
*/
@NonNull
public IntentBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
public NewEngineIntentBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
this.dartEntrypoint = dartEntrypoint;
return this;
}
......@@ -214,7 +248,7 @@ public class FlutterActivity extends Activity
* defaults to "/".
*/
@NonNull
public IntentBuilder initialRoute(@NonNull String initialRoute) {
public NewEngineIntentBuilder initialRoute(@NonNull String initialRoute) {
this.initialRoute = initialRoute;
return this;
}
......@@ -236,7 +270,7 @@ public class FlutterActivity extends Activity
* following property: {@code <item name="android:windowIsTranslucent">true</item>}.
*/
@NonNull
public IntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
public NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
this.backgroundMode = backgroundMode.name();
return this;
}
......@@ -250,6 +284,93 @@ public class FlutterActivity extends Activity
return new Intent(context, activityClass)
.putExtra(EXTRA_DART_ENTRYPOINT, dartEntrypoint)
.putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
.putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
.putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
}
}
/**
* Creates a {@link CachedEngineIntentBuilder}, which can be used to configure an {@link Intent}
* to launch a {@code FlutterActivity} that internally uses an existing {@link FlutterEngine} that
* is cached in {@link FlutterEngineCache}.
*/
public static CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId) {
return new CachedEngineIntentBuilder(FlutterActivity.class, cachedEngineId);
}
/**
* Builder to create an {@code Intent} that launches a {@code FlutterActivity} with an existing
* {@link FlutterEngine} that is cached in {@link FlutterEngineCache}.
*/
public static class CachedEngineIntentBuilder {
private final Class<? extends FlutterActivity> activityClass;
private final String cachedEngineId;
private boolean destroyEngineWithActivity = false;
private String backgroundMode = DEFAULT_BACKGROUND_MODE;
/**
* Constructor that allows this {@code CachedEngineIntentBuilder} to be used by subclasses of
* {@code FlutterActivity}.
* <p>
* Subclasses of {@code FlutterActivity} should provide their own static version of
* {@link #withNewEngine()}, which returns an instance of {@code CachedEngineIntentBuilder}
* constructed with a {@code Class} reference to the {@code FlutterActivity} subclass,
* e.g.:
* <p>
* {@code
* return new CachedEngineIntentBuilder(MyFlutterActivity.class, engineId);
* }
*/
protected CachedEngineIntentBuilder(
@NonNull Class<? extends FlutterActivity> activityClass,
@NonNull String engineId
) {
this.activityClass = activityClass;
this.cachedEngineId = engineId;
}
/**
* Returns true if the cached {@link FlutterEngine} should be destroyed and removed from the
* cache when this {@code FlutterActivity} is destroyed.
* <p>
* The default value is {@code false}.
*/
public CachedEngineIntentBuilder destroyEngineWithActivity(boolean destroyEngineWithActivity) {
this.destroyEngineWithActivity = destroyEngineWithActivity;
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>}.
*/
@NonNull
public CachedEngineIntentBuilder 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.
*/
@NonNull
public Intent build(@NonNull Context context) {
return new Intent(context, activityClass)
.putExtra(EXTRA_CACHED_ENGINE_ID, cachedEngineId)
.putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, destroyEngineWithActivity)
.putExtra(EXTRA_BACKGROUND_MODE, backgroundMode);
}
}
......@@ -522,6 +643,31 @@ public class FlutterActivity extends Activity
return FlutterShellArgs.fromIntent(getIntent());
}
/**
* Returns the ID of a statically cached {@link FlutterEngine} to use within this
* {@code FlutterActivity}, or {@code null} if this {@code FlutterActivity} does not want to
* use a cached {@link FlutterEngine}.
*/
@Override
@Nullable
public String getCachedEngineId() {
return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID);
}
/**
* Returns false if the {@link FlutterEngine} backing this {@code FlutterActivity} should
* outlive this {@code FlutterActivity}, or true to be destroyed when the {@code FlutterActivity}
* is destroyed.
* <p>
* The default value is {@code true} in cases where {@code FlutterActivity} created its own
* {@link FlutterEngine}, and {@code false} in cases where a cached {@link FlutterEngine} was
* provided.
*/
@Override
public boolean shouldDestroyEngineWithHost() {
return getIntent().getBooleanExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, false);
}
/**
* The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded.
* <p>
......@@ -753,16 +899,6 @@ public class FlutterActivity extends Activity
return true;
}
/**
* Returns true if the {@link FlutterEngine} backing this {@code FlutterActivity} should
* outlive this {@code FlutterActivity}, or be destroyed when the {@code FlutterActivity}
* is destroyed.
*/
@Override
public boolean retainFlutterEngineAfterHostDestruction() {
return false;
}
@Override
public void onFirstFrameRendered() {}
......
......@@ -22,6 +22,7 @@ import java.util.Arrays;
import io.flutter.Log;
import io.flutter.app.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
......@@ -182,7 +183,11 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
/**
* Obtains a reference to a FlutterEngine to back this delegate and its {@code host}.
* <p>
* First, the {@code host} is given an opportunity to provide a {@link FlutterEngine} via
* <p>
* First, the {@code host} is asked if it would like to use a cached {@link FlutterEngine}, and
* if so, the cached {@link FlutterEngine} is retrieved.
* <p>
* Second, the {@code host} is given an opportunity to provide a {@link FlutterEngine} via
* {@link Host#provideFlutterEngine(Context)}.
* <p>
* If the {@code host} does not provide a {@link FlutterEngine}, then a new {@link FlutterEngine}
......@@ -191,9 +196,21 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
private void setupFlutterEngine() {
Log.d(TAG, "Setting up FlutterEngine.");
// First, defer to subclasses for a custom FlutterEngine.
// First, check if the host wants to use a cached FlutterEngine.
String cachedEngineId = host.getCachedEngineId();
if (cachedEngineId != null) {
flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
isFlutterEngineFromHost = true;
if (flutterEngine == null) {
throw new IllegalStateException("The requested cached FlutterEngine did not exist in the FlutterEngineCache: '" + cachedEngineId + "'");
}
return;
}
// Second, defer to subclasses for a custom FlutterEngine.
flutterEngine = host.provideFlutterEngine(host.getContext());
if (flutterEngine != null) {
isFlutterEngineFromHost = true;
return;
}
......@@ -275,6 +292,11 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
* {@code flutterEngine} must be non-null when invoking this method.
*/
private void doInitialFlutterViewRun() {
// Don't attempt to start a FlutterEngine if we're using a cached FlutterEngine.
if (host.getCachedEngineId() != null) {
return;
}
if (flutterEngine.getDartExecutor().isExecutingDart()) {
// No warning is logged because this situation will happen on every config
// change if the developer does not choose to retain the Fragment instance.
......@@ -387,7 +409,7 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
* if it was previously attached.</li>
* <li>Destroys this delegate's {@link PlatformPlugin}.</li>
* <li>Destroys this delegate's {@link FlutterEngine} if
* {@link Host#retainFlutterEngineAfterHostDestruction()} returns false.</li>
* {@link Host#shouldDestroyEngineWithHost()} ()} returns true.</li>
* </ol>
*/
void onDetach() {
......@@ -412,8 +434,13 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
}
// Destroy our FlutterEngine if we're not set to retain it.
if (!host.retainFlutterEngineAfterHostDestruction() && !isFlutterEngineFromHost) {
if (host.shouldDestroyEngineWithHost()) {
flutterEngine.destroy();
if (host.getCachedEngineId() != null) {
FlutterEngineCache.getInstance().remove(host.getCachedEngineId());
}
flutterEngine = null;
}
}
......@@ -587,6 +614,24 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
@NonNull
FlutterShellArgs getFlutterShellArgs();
/**
* Returns the ID of a statically cached {@link FlutterEngine} to use within this
* delegate's host, or {@code null} if this delegate's host does not want to
* use a cached {@link FlutterEngine}.
*/
@Nullable
String getCachedEngineId();
/**
* Returns true if the {@link FlutterEngine} used in this delegate should be destroyed
* when the host/delegate are destroyed.
* <p>
* The default value is {@code true} in cases where {@code FlutterFragment} created its own
* {@link FlutterEngine}, and {@code false} in cases where a cached {@link FlutterEngine} was
* provided.
*/
boolean shouldDestroyEngineWithHost();
/**
* Returns the Dart entrypoint that should run when a new {@link FlutterEngine} is
* created.
......@@ -649,15 +694,6 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
*/
boolean shouldAttachEngineToActivity();
/**
* Returns true if the {@link FlutterEngine} used in this delegate should outlive the
* delegate.
* <p>
* If {@code false} is returned, the {@link FlutterEngine} used in this delegate will be
* destroyed when the delegate is destroyed.
*/
boolean retainFlutterEngineAfterHostDestruction();
/**
* Invoked by this delegate when its {@link FlutterView} has rendered its first Flutter
* frame.
......
// 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 android.arch.lifecycle.DefaultLifecycleObserver;
......
// 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 android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import java.util.HashMap;
import java.util.Map;
/**
* Static singleton cache that holds {@link FlutterEngine} instances identified by {@code String}s.
* <p>
* The ID of a given {@link FlutterEngine} can be whatever {@code String} is desired.
* <p>
* {@code FlutterEngineCache} is useful for storing pre-warmed {@link FlutterEngine} instances.
* {@link io.flutter.embedding.android.FlutterActivity} and
* {@link io.flutter.embedding.android.FlutterFragment} use the {@code FlutterEngineCache} singleton
* internally when instructed to use a cached {@link FlutterEngine} based on a given ID. See
* {@link io.flutter.embedding.android.FlutterActivity.CachedEngineIntentBuilder} and
* {@link io.flutter.embedding.android.FlutterFragment#withCachedEngine(String)} for related APIs.
*/
public class FlutterEngineCache {
private static FlutterEngineCache instance;
/**
* Returns the static singleton instance of {@code FlutterEngineCache}.
* <p>
* Creates a new instance if one does not yet exist.
*/
@NonNull
public static FlutterEngineCache getInstance() {
if (instance == null) {
instance = new FlutterEngineCache();
}
return instance;
}
private final Map<String, FlutterEngine> cachedEngines = new HashMap<>();
@VisibleForTesting
/* package */ FlutterEngineCache() {}
/**
* Returns {@code true} if a {@link FlutterEngine} in this cache is associated with the
* given {@code engineId}.
*/
public boolean contains(@NonNull String engineId) {
return cachedEngines.containsKey(engineId);
}
/**
* Returns the {@link FlutterEngine} in this cache that is associated with the given
* {@code engineId}, or {@code null} is no such {@link FlutterEngine} exists.
*/
@Nullable
public FlutterEngine get(@NonNull String engineId) {
return cachedEngines.get(engineId);
}
/**
* Places the given {@link FlutterEngine} in this cache and associates it with the given
* {@code engineId}.
* <p>
* If a {@link FlutterEngine} already exists in this cache for the given {@code engineId}, that
* {@link FlutterEngine} is removed from this cache.
*/
public void put(@NonNull String engineId, @Nullable FlutterEngine engine) {
if (engine != null) {
cachedEngines.put(engineId, engine);
} else {
cachedEngines.remove(engineId);
}
}
/**
* Removes any {@link FlutterEngine} that is currently in the cache that is identified by
* the given {@code engineId}.
*/
public void remove(@NonNull String engineId) {
put(engineId, null);
}
}
......@@ -4,19 +4,24 @@
package io.flutter;
import io.flutter.SmokeTest;
import io.flutter.util.PreconditionsTest;
import io.flutter.embedding.android.FlutterActivityAndFragmentDelegateTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import io.flutter.embedding.android.FlutterActivityAndFragmentDelegateTest;
import io.flutter.embedding.android.FlutterActivityTest;
import io.flutter.embedding.android.FlutterFragmentTest;
import io.flutter.embedding.engine.FlutterEngineCacheTest;
import io.flutter.util.PreconditionsTest;
@RunWith(Suite.class)
@SuiteClasses({
PreconditionsTest.class,
SmokeTest.class,
FlutterActivityTest.class,
FlutterFragmentTest.class,
FlutterActivityAndFragmentDelegateTest.class,
FlutterEngineCacheTest.class
})
/** Runs all of the unit tests listed in the {@code @SuiteClasses} annotation. */
public class FlutterTestSuite {}
package io.flutter.embedding.android;
import android.content.Intent;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@Config(manifest=Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class FlutterActivityTest {
@Test
public void itCreatesDefaultIntentWithExpectedDefaults() {
Intent intent = FlutterActivity.createDefaultIntent(RuntimeEnvironment.application);
ActivityController<FlutterActivity> activityController = Robolectric.buildActivity(FlutterActivity.class, intent);
FlutterActivity flutterActivity = activityController.get();
assertEquals("main", flutterActivity.getDartEntrypointFunctionName());
assertEquals("/", flutterActivity.getInitialRoute());
assertArrayEquals(new String[]{}, flutterActivity.getFlutterShellArgs().toArray());
assertTrue(flutterActivity.shouldAttachEngineToActivity());
assertNull(flutterActivity.getCachedEngineId());
assertTrue(flutterActivity.shouldDestroyEngineWithHost());
assertEquals(FlutterActivity.BackgroundMode.opaque, flutterActivity.getBackgroundMode());
assertEquals(FlutterView.RenderMode.surface, flutterActivity.getRenderMode());
assertEquals(FlutterView.TransparencyMode.opaque, flutterActivity.getTransparencyMode());
}
@Test
public void itCreatesNewEngineIntentWithRequestedSettings() {
Intent intent = FlutterActivity.withNewEngine()
.dartEntrypoint("custom_entrypoint")
.initialRoute("/custom/route")
.backgroundMode(FlutterActivity.BackgroundMode.transparent)
.build(RuntimeEnvironment.application);
ActivityController<FlutterActivity> activityController = Robolectric.buildActivity(FlutterActivity.class, intent);
FlutterActivity flutterActivity = activityController.get();
assertEquals("custom_entrypoint", flutterActivity.getDartEntrypointFunctionName());
assertEquals("/custom/route", flutterActivity.getInitialRoute());
assertArrayEquals(new String[]{}, flutterActivity.getFlutterShellArgs().toArray());
assertTrue(flutterActivity.shouldAttachEngineToActivity());
assertNull(flutterActivity.getCachedEngineId());
assertTrue(flutterActivity.shouldDestroyEngineWithHost());
assertEquals(FlutterActivity.BackgroundMode.transparent, flutterActivity.getBackgroundMode());
assertEquals(FlutterView.RenderMode.texture, flutterActivity.getRenderMode());
assertEquals(FlutterView.TransparencyMode.transparent, flutterActivity.getTransparencyMode());
}
@Test
public void itCreatesCachedEngineIntentThatDoesNotDestroyTheEngine() {
Intent intent = FlutterActivity.withCachedEngine("my_cached_engine")
.destroyEngineWithActivity(false)
.build(RuntimeEnvironment.application);
ActivityController<FlutterActivity> activityController = Robolectric.buildActivity(FlutterActivity.class, intent);
FlutterActivity flutterActivity = activityController.get();
assertArrayEquals(new String[]{}, flutterActivity.getFlutterShellArgs().toArray());
assertTrue(flutterActivity.shouldAttachEngineToActivity());
assertEquals("my_cached_engine", flutterActivity.getCachedEngineId());
assertFalse(flutterActivity.shouldDestroyEngineWithHost());
}
@Test
public void itCreatesCachedEngineIntentThatDestroysTheEngine() {
Intent intent = FlutterActivity.withCachedEngine("my_cached_engine")
.destroyEngineWithActivity(true)
.build(RuntimeEnvironment.application);
ActivityController<FlutterActivity> activityController = Robolectric.buildActivity(FlutterActivity.class, intent);
FlutterActivity flutterActivity = activityController.get();
assertArrayEquals(new String[]{}, flutterActivity.getFlutterShellArgs().toArray());
assertTrue(flutterActivity.shouldAttachEngineToActivity());
assertEquals("my_cached_engine", flutterActivity.getCachedEngineId());
assertTrue(flutterActivity.shouldDestroyEngineWithHost());
}
}
package io.flutter.embedding.android;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@Config(manifest=Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class FlutterFragmentTest {
@Test
public void itCreatesDefaultFragmentWithExpectedDefaults() {
FlutterFragment fragment = FlutterFragment.createDefault();
assertEquals("main", fragment.getDartEntrypointFunctionName());
assertEquals("/", fragment.getInitialRoute());
assertArrayEquals(new String[]{}, fragment.getFlutterShellArgs().toArray());
assertTrue(fragment.shouldAttachEngineToActivity());
assertNull(fragment.getCachedEngineId());
assertTrue(fragment.shouldDestroyEngineWithHost());
assertEquals(FlutterView.RenderMode.surface, fragment.getRenderMode());
assertEquals(FlutterView.TransparencyMode.transparent, fragment.getTransparencyMode());
}
@Test
public void itCreatesNewEngineFragmentWithRequestedSettings() {
FlutterFragment fragment = FlutterFragment.withNewEngine()
.dartEntrypoint("custom_entrypoint")
.initialRoute("/custom/route")
.shouldAttachEngineToActivity(false)
.renderMode(FlutterView.RenderMode.texture)
.transparencyMode(FlutterView.TransparencyMode.opaque)
.build();
assertEquals("custom_entrypoint", fragment.getDartEntrypointFunctionName());
assertEquals("/custom/route", fragment.getInitialRoute());
assertArrayEquals(new String[]{}, fragment.getFlutterShellArgs().toArray());
assertFalse(fragment.shouldAttachEngineToActivity());
assertNull(fragment.getCachedEngineId());
assertTrue(fragment.shouldDestroyEngineWithHost());
assertEquals(FlutterView.RenderMode.texture, fragment.getRenderMode());
assertEquals(FlutterView.TransparencyMode.opaque, fragment.getTransparencyMode());
}
@Test
public void itCreatesCachedEngineFragmentThatDoesNotDestroyTheEngine() {
FlutterFragment fragment = FlutterFragment
.withCachedEngine("my_cached_engine")
.build();
assertTrue(fragment.shouldAttachEngineToActivity());
assertEquals("my_cached_engine", fragment.getCachedEngineId());
assertFalse(fragment.shouldDestroyEngineWithHost());
}
@Test
public void itCreatesCachedEngineFragmentThatDestroysTheEngine() {
FlutterFragment fragment = FlutterFragment
.withCachedEngine("my_cached_engine")
.destroyEngineWithFragment(true)
.build();
assertTrue(fragment.shouldAttachEngineToActivity());
assertEquals("my_cached_engine", fragment.getCachedEngineId());
assertTrue(fragment.shouldDestroyEngineWithHost());
}
}
package io.flutter.embedding.engine;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
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;
@Config(manifest=Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class FlutterEngineCacheTest {
@Test
public void itHoldsFlutterEngines() {
// --- Test Setup ---
FlutterEngine flutterEngine = mock(FlutterEngine.class);
FlutterEngineCache cache = new FlutterEngineCache();
// --- Execute Test ---
cache.put("my_flutter_engine", flutterEngine);
// --- Verify Results ---
assertEquals(flutterEngine, cache.get("my_flutter_engine"));
}
@Test
public void itQueriesFlutterEngineExistence() {
// --- Test Setup ---
FlutterEngine flutterEngine = mock(FlutterEngine.class);
FlutterEngineCache cache = new FlutterEngineCache();
// --- Execute Test ---
assertFalse(cache.contains("my_flutter_engine"));
cache.put("my_flutter_engine", flutterEngine);
// --- Verify Results ---
assertTrue(cache.contains("my_flutter_engine"));
}
@Test
public void itRemovesFlutterEngines() {
// --- Test Setup ---
FlutterEngine flutterEngine = mock(FlutterEngine.class);
FlutterEngineCache cache = new FlutterEngineCache();
// --- Execute Test ---
cache.put("my_flutter_engine", flutterEngine);
cache.remove("my_flutter_engine");
// --- Verify Results ---
assertNull(cache.get("my_flutter_engine"));
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册