Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
sxychenjing
engine
提交
735255f3
E
engine
项目概览
sxychenjing
/
engine
与 Fork 源项目一致
从无法访问的项目Fork
通知
3
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
E
engine
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
未验证
提交
735255f3
编写于
8月 06, 2019
作者:
M
Matt Carroll
提交者:
GitHub
8月 06, 2019
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Android embedding refactor pr40 add static engine cache (#10481)
上级
b0f7c2b6
变更
13
隐藏空白更改
内联
并排
Showing
13 changed file
with
1040 addition
and
234 deletion
+1040
-234
DEPS
DEPS
+1
-1
ci/licenses_golden/licenses_flutter
ci/licenses_golden/licenses_flutter
+1
-0
shell/platform/android/BUILD.gn
shell/platform/android/BUILD.gn
+5
-0
shell/platform/android/io/flutter/embedding/android/FlutterActivity.java
...android/io/flutter/embedding/android/FlutterActivity.java
+164
-28
shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
...embedding/android/FlutterActivityAndFragmentDelegate.java
+49
-13
shell/platform/android/io/flutter/embedding/android/FlutterFragment.java
...android/io/flutter/embedding/android/FlutterFragment.java
+307
-45
shell/platform/android/io/flutter/embedding/engine/FlutterEngineAndroidLifecycle.java
...utter/embedding/engine/FlutterEngineAndroidLifecycle.java
+4
-0
shell/platform/android/io/flutter/embedding/engine/FlutterEngineCache.java
...droid/io/flutter/embedding/engine/FlutterEngineCache.java
+86
-0
shell/platform/android/test/io/flutter/FlutterTestSuite.java
shell/platform/android/test/io/flutter/FlutterTestSuite.java
+9
-4
shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java
...dding/android/FlutterActivityAndFragmentDelegateTest.java
+196
-143
shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java
...est/io/flutter/embedding/android/FlutterActivityTest.java
+87
-0
shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java
...est/io/flutter/embedding/android/FlutterFragmentTest.java
+73
-0
shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineCacheTest.java
...t/io/flutter/embedding/engine/FlutterEngineCacheTest.java
+58
-0
未找到文件。
DEPS
浏览文件 @
735255f3
...
...
@@ -481,7 +481,7 @@ deps = {
'packages': [
{
'package': 'flutter/android/robolectric_bundle',
'version': 'last_updated:2019-0
7-29T15:27:42
-0700'
'version': 'last_updated:2019-0
8-02T16:01:27
-0700'
}
],
'condition': 'download_android_deps',
...
...
ci/licenses_golden/licenses_flutter
浏览文件 @
735255f3
...
...
@@ -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
...
...
shell/platform/android/BUILD.gn
浏览文件 @
735255f3
...
...
@@ -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",
]
...
...
shell/platform/android/io/flutter/embedding/android/FlutterActivity.java
浏览文件 @
735255f3
...
...
@@ -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
NewEngine
IntentBuilder}
* via the following methods:
* <ul>
* <li>{@link IntentBuilder#dartEntrypoint}</li>
* <li>{@link IntentBuilder#initialRoute}</li>
* <li>{@link
NewEngine
IntentBuilder#dartEntrypoint}</li>
* <li>{@link
NewEngine
IntentBuilder#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
NewEngine
IntentBuilder
(
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
NewEngine
IntentBuilder
{
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
NewEngine
IntentBuilder} 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 NewEngine
IntentBuilder}
* constructed with a {@code Class} reference to the {@code FlutterActivity} subclass,
* e.g.:
* <p>
* {@code
* return new IntentBuilder(MyFlutterActivity.class);
* return new
NewEngine
IntentBuilder(MyFlutterActivity.class);
* }
*/
protected
IntentBuilder
(
@NonNull
Class
<?
extends
FlutterActivity
>
activityClass
)
{
protected
NewEngine
IntentBuilder
(
@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
NewEngine
IntentBuilder
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
NewEngine
IntentBuilder
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
NewEngine
IntentBuilder
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
()
{}
...
...
shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
浏览文件 @
735255f3
...
...
@@ -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 fals
e.</li>
* {@link Host#
shouldDestroyEngineWithHost()} ()} returns tru
e.</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.
...
...
shell/platform/android/io/flutter/embedding/android/FlutterFragment.java
浏览文件 @
735255f3
...
...
@@ -47,6 +47,34 @@ import io.flutter.view.FlutterMain;
* If convenient, consider using a {@link FlutterActivity} instead of a {@code FlutterFragment} to
* avoid the work of forwarding calls.
* <p>
* {@code FlutterFragment} supports the use of an existing, cached {@link FlutterEngine}. To use a
* cached {@link FlutterEngine}, ensure that the {@link FlutterEngine} is stored in
* {@link FlutterEngineCache} and then use {@link #withCachedEngine(String)} to build a
* {@code FlutterFragment} with the cached {@link FlutterEngine}'s ID.
* <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 FlutterFragment} is in 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 can only use a {@code View}, consider using a
* {@link FlutterView}. Using a {@link FlutterView} requires forwarding some calls from an
* {@code Activity}, as well as forwarding lifecycle calls from an {@code Activity} or a
...
...
@@ -85,45 +113,83 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
* See {@link #shouldAttachEngineToActivity()}.
*/
protected
static
final
String
ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY
=
"should_attach_engine_to_activity"
;
/**
* The ID of a {@link FlutterEngine} cached in {@link FlutterEngineCache} that will be used within
* the created {@code FlutterFragment}.
*/
protected
static
final
String
ARG_CACHED_ENGINE_ID
=
"cached_engine_id"
;
/**
* True if the {@link FlutterEngine} in the created {@code FlutterFragment} should be destroyed
* when the {@code FlutterFragment} is destroyed, false if the {@link FlutterEngine} should
* outlive the {@code FlutterFragment}.
*/
protected
static
final
String
ARG_DESTROY_ENGINE_WITH_FRAGMENT
=
"destroy_engine_with_fragment"
;
/**
* Creates a {@code FlutterFragment} with a default configuration.
* <p>
* {@code FlutterFragment}'s default configuration creates a new {@link FlutterEngine} within
* the {@code FlutterFragment} and uses the following settings:
* <ul>
* <li>Dart entrypoint: "main"</li>
* <li>Initial route: "/"</li>
* <li>Render mode: surface</li>
* <li>Transparency mode: transparent</li>
* </ul>
* <p>
* To use a new {@link FlutterEngine} with different settings, use {@link #withNewEngine()}.
* <p>
* To use a cached {@link FlutterEngine} instead of creating a new one, use
* {@link #withCachedEngine(String)}.
*/
@NonNull
public
static
FlutterFragment
createDefault
()
{
return
new
NewEngineFragmentBuilder
().
build
();
}
/**
* Returns a {@link NewEngineFragmentBuilder} to create a {@code FlutterFragment} with a new
* {@link FlutterEngine} and a desired engine configuration.
*/
@NonNull
public
static
FlutterFragment
createDefaultFlutterFragment
()
{
return
new
FlutterFragment
.
Builder
().
build
();
public
static
NewEngineFragmentBuilder
withNewEngine
()
{
return
new
NewEngineFragmentBuilder
();
}
/**
* Builder that creates a new {@code FlutterFragment} with {@code arguments} that correspond
* to the values set on this {@code Builder}.
* to the values set on this {@code
NewEngineFragment
Builder}.
* <p>
* To create a {@code FlutterFragment} with default {@code arguments}, invoke
* {@link #createDefault
FlutterFragment
()}.
* {@link #createDefault()}.
* <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 NewEngineFragmentBuilder} to construct instances of the subclass without subclassing
* this {@code NewEngineFragmentBuilder}.
* {@code
* MyFlutterFragment f = new FlutterFragment.Builder(MyFlutterFragment.class)
* MyFlutterFragment f = new FlutterFragment.
NewEngineFragment
Builder(MyFlutterFragment.class)
* .someProperty(...)
* .someOtherProperty(...)
* .build<MyFlutterFragment>();
* }
* <p>
* Subclasses of {@code FlutterFragment} that introduce new arguments should subclass this
* {@code Builder} to add the new properties:
* {@code
NewEngineFragment
Builder} to add the new properties:
* <ol>
* <li>Ensure the {@code FlutterFragment} subclass has a no-arg constructor.</li>
* <li>Subclass this {@code Builder}.</li>
* <li>Override the new {@code
Builder}'s no-arg constructor and invoke the super constructor
* to set the {@code FlutterFragment} subclass: {@code
* <li>Subclass this {@code
NewEngineFragment
Builder}.</li>
* <li>Override the new {@code
NewEngineFragmentBuilder}'s no-arg constructor and invoke the
*
super constructor
to set the {@code FlutterFragment} subclass: {@code
* public MyBuilder() {
* super(MyFlutterFragment.class);
* }
* }</li>
* <li>Add appropriate property methods for the new properties.</li>
* <li>Override {@link
Builder#createArgs()}, call through to the super method, then add
* the new properties as arguments in the {@link Bundle}.</li>
* <li>Override {@link
NewEngineFragmentBuilder#createArgs()}, call through to the super method,
* the
n add the
new properties as arguments in the {@link Bundle}.</li>
* </ol>
* Once a {@code
Builder} subclass is defined, the {@code FlutterFragment} subclass can be
* instantiated as follows.
* Once a {@code
NewEngineFragmentBuilder} subclass is defined, the {@code FlutterFragment}
*
subclass can be
instantiated as follows.
* {@code
* MyFlutterFragment f = new MyBuilder()
* .someExistingProperty(...)
...
...
@@ -131,7 +197,7 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
* .build<MyFlutterFragment>();
* }
*/
public
static
class
Builder
{
public
static
class
NewEngineFragment
Builder
{
private
final
Class
<?
extends
FlutterFragment
>
fragmentClass
;
private
String
dartEntrypoint
=
"main"
;
private
String
initialRoute
=
"/"
;
...
...
@@ -142,18 +208,18 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
private
boolean
shouldAttachEngineToActivity
=
true
;
/**
* Constructs a {@code Builder} that is configured to construct an instance of
* Constructs a {@code
NewEngineFragment
Builder} that is configured to construct an instance of
* {@code FlutterFragment}.
*/
public
Builder
()
{
public
NewEngineFragment
Builder
()
{
fragmentClass
=
FlutterFragment
.
class
;
}
/**
* Constructs a {@code Builder} that is configured to construct an instance of
* Constructs a {@code
NewEngineFragment
Builder} that is configured to construct an instance of
* {@code subclass}, which extends {@code FlutterFragment}.
*/
public
Builder
(
@NonNull
Class
<?
extends
FlutterFragment
>
subclass
)
{
public
NewEngineFragment
Builder
(
@NonNull
Class
<?
extends
FlutterFragment
>
subclass
)
{
fragmentClass
=
subclass
;
}
...
...
@@ -161,7 +227,7 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
* The name of the initial Dart method to invoke, defaults to "main".
*/
@NonNull
public
Builder
dartEntrypoint
(
@NonNull
String
dartEntrypoint
)
{
public
NewEngineFragment
Builder
dartEntrypoint
(
@NonNull
String
dartEntrypoint
)
{
this
.
dartEntrypoint
=
dartEntrypoint
;
return
this
;
}
...
...
@@ -171,7 +237,7 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
* defaults to "/".
*/
@NonNull
public
Builder
initialRoute
(
@NonNull
String
initialRoute
)
{
public
NewEngineFragment
Builder
initialRoute
(
@NonNull
String
initialRoute
)
{
this
.
initialRoute
=
initialRoute
;
return
this
;
}
...
...
@@ -181,7 +247,7 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
* to {@link FlutterMain#findAppBundlePath()}
*/
@NonNull
public
Builder
appBundlePath
(
@NonNull
String
appBundlePath
)
{
public
NewEngineFragment
Builder
appBundlePath
(
@NonNull
String
appBundlePath
)
{
this
.
appBundlePath
=
appBundlePath
;
return
this
;
}
...
...
@@ -190,7 +256,7 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
* Any special configuration arguments for the Flutter engine
*/
@NonNull
public
Builder
flutterShellArgs
(
@NonNull
FlutterShellArgs
shellArgs
)
{
public
NewEngineFragment
Builder
flutterShellArgs
(
@NonNull
FlutterShellArgs
shellArgs
)
{
this
.
shellArgs
=
shellArgs
;
return
this
;
}
...
...
@@ -204,7 +270,7 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
* cannot.
*/
@NonNull
public
Builder
renderMode
(
@NonNull
FlutterView
.
RenderMode
renderMode
)
{
public
NewEngineFragment
Builder
renderMode
(
@NonNull
FlutterView
.
RenderMode
renderMode
)
{
this
.
renderMode
=
renderMode
;
return
this
;
}
...
...
@@ -216,7 +282,7 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
* See {@link FlutterView.TransparencyMode} for implications of this selection.
*/
@NonNull
public
Builder
transparencyMode
(
@NonNull
FlutterView
.
TransparencyMode
transparencyMode
)
{
public
NewEngineFragment
Builder
transparencyMode
(
@NonNull
FlutterView
.
TransparencyMode
transparencyMode
)
{
this
.
transparencyMode
=
transparencyMode
;
return
this
;
}
...
...
@@ -256,7 +322,7 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
* setting {@code shouldAttachEngineToActivity} to {@code false}.
*/
@NonNull
public
Builder
shouldAttachEngineToActivity
(
boolean
shouldAttachEngineToActivity
)
{
public
NewEngineFragment
Builder
shouldAttachEngineToActivity
(
boolean
shouldAttachEngineToActivity
)
{
this
.
shouldAttachEngineToActivity
=
shouldAttachEngineToActivity
;
return
this
;
}
...
...
@@ -280,6 +346,7 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
args
.
putString
(
ARG_FLUTTERVIEW_RENDER_MODE
,
renderMode
!=
null
?
renderMode
.
name
()
:
FlutterView
.
RenderMode
.
surface
.
name
());
args
.
putString
(
ARG_FLUTTERVIEW_TRANSPARENCY_MODE
,
transparencyMode
!=
null
?
transparencyMode
.
name
()
:
FlutterView
.
TransparencyMode
.
transparent
.
name
());
args
.
putBoolean
(
ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY
,
shouldAttachEngineToActivity
);
args
.
putBoolean
(
ARG_DESTROY_ENGINE_WITH_FRAGMENT
,
true
);
return
args
;
}
...
...
@@ -307,6 +374,194 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
}
}
/**
* Returns a {@link CachedEngineFragmentBuilder} to create a {@code FlutterFragment} with a cached
* {@link FlutterEngine} in {@link FlutterEngineCache}.
* <p>
* An {@code IllegalStateException} will be thrown during the lifecycle of the
* {@code FlutterFragment} if a cached {@link FlutterEngine} is requested but does not exist in
* the cache.
* <p>
* To create a {@code FlutterFragment} that uses a new {@link FlutterEngine}, use
* {@link #createDefault()} or {@link #withNewEngine()}.
*/
@NonNull
public
static
CachedEngineFragmentBuilder
withCachedEngine
(
@NonNull
String
engineId
)
{
return
new
CachedEngineFragmentBuilder
(
engineId
);
}
/**
* Builder that creates a new {@code FlutterFragment} that uses a cached {@link 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.CachedEngineFragmentBuilder(MyFlutterFragment.class)
* .someProperty(...)
* .someOtherProperty(...)
* .build<MyFlutterFragment>();
* }
* <p>
* Subclasses of {@code FlutterFragment} that introduce new arguments should subclass this
* {@code CachedEngineFragmentBuilder} to add the new properties:
* <ol>
* <li>Ensure the {@code FlutterFragment} subclass has a no-arg constructor.</li>
* <li>Subclass this {@code CachedEngineFragmentBuilder}.</li>
* <li>Override the new {@code CachedEngineFragmentBuilder}'s no-arg constructor and invoke the
* super constructor to set the {@code FlutterFragment} subclass: {@code
* public MyBuilder() {
* super(MyFlutterFragment.class);
* }
* }</li>
* <li>Add appropriate property methods for the new properties.</li>
* <li>Override {@link CachedEngineFragmentBuilder#createArgs()}, call through to the super
* method, then add the new properties as arguments in the {@link Bundle}.</li>
* </ol>
* Once a {@code CachedEngineFragmentBuilder} subclass is defined, the {@code FlutterFragment}
* subclass can be instantiated as follows.
* {@code
* MyFlutterFragment f = new MyBuilder()
* .someExistingProperty(...)
* .someNewProperty(...)
* .build<MyFlutterFragment>();
* }
*/
public
static
class
CachedEngineFragmentBuilder
{
private
final
Class
<?
extends
FlutterFragment
>
fragmentClass
;
private
final
String
engineId
;
private
boolean
destroyEngineWithFragment
=
false
;
private
FlutterView
.
RenderMode
renderMode
=
FlutterView
.
RenderMode
.
surface
;
private
FlutterView
.
TransparencyMode
transparencyMode
=
FlutterView
.
TransparencyMode
.
transparent
;
private
boolean
shouldAttachEngineToActivity
=
true
;
private
CachedEngineFragmentBuilder
(
@NonNull
String
engineId
)
{
this
(
FlutterFragment
.
class
,
engineId
);
}
protected
CachedEngineFragmentBuilder
(
@NonNull
Class
<?
extends
FlutterFragment
>
subclass
,
@NonNull
String
engineId
)
{
this
.
fragmentClass
=
subclass
;
this
.
engineId
=
engineId
;
}
/**
* Pass {@code true} to destroy the cached {@link FlutterEngine} when this
* {@code FlutterFragment} is destroyed, or {@code false} for the cached {@link FlutterEngine}
* to outlive this {@code FlutterFragment}.
*/
@NonNull
public
CachedEngineFragmentBuilder
destroyEngineWithFragment
(
boolean
destroyEngineWithFragment
)
{
this
.
destroyEngineWithFragment
=
destroyEngineWithFragment
;
return
this
;
}
/**
* Render Flutter either as a {@link FlutterView.RenderMode#surface} or a
* {@link FlutterView.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
CachedEngineFragmentBuilder
renderMode
(
@NonNull
FlutterView
.
RenderMode
renderMode
)
{
this
.
renderMode
=
renderMode
;
return
this
;
}
/**
* Support a {@link FlutterView.TransparencyMode#transparent} background within {@link FlutterView},
* or force an {@link FlutterView.TransparencyMode#opaque} background.
* <p>
* See {@link FlutterView.TransparencyMode} for implications of this selection.
*/
@NonNull
public
CachedEngineFragmentBuilder
transparencyMode
(
@NonNull
FlutterView
.
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 FlutterEngine}.
* <p>
* Control surfaces are used to provide Android resources and lifecycle events to
* plugins that are attached to the {@link FlutterEngine}. If {@code shouldAttachEngineToActivity}
* is true then this {@code FlutterFragment} will connect its {@link 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 FlutterEngine} and the surrounding
* {@code Activity}. The {@code Activity} will need to be manually connected to this
* {@code FlutterFragment}'s {@link 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 FlutterEngine} is if the developer wants to move the
* {@link FlutterEngine} somewhere else. For example, a developer might want the
* {@link FlutterEngine} to outlive the surrounding {@code Activity} so that it can be used
* later in a different {@code Activity}. To accomplish this, the {@link 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 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 FlutterEngine} is if the developer wants to prevent, or explicitly
* control when the {@link 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 FlutterEngine} to have access to the {@code Activity}, which can be accomplished by
* setting {@code shouldAttachEngineToActivity} to {@code false}.
*/
@NonNull
public
CachedEngineFragmentBuilder
shouldAttachEngineToActivity
(
boolean
shouldAttachEngineToActivity
)
{
this
.
shouldAttachEngineToActivity
=
shouldAttachEngineToActivity
;
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_ID
,
engineId
);
args
.
putBoolean
(
ARG_DESTROY_ENGINE_WITH_FRAGMENT
,
destroyEngineWithFragment
);
args
.
putString
(
ARG_FLUTTERVIEW_RENDER_MODE
,
renderMode
!=
null
?
renderMode
.
name
()
:
FlutterView
.
RenderMode
.
surface
.
name
());
args
.
putString
(
ARG_FLUTTERVIEW_TRANSPARENCY_MODE
,
transparencyMode
!=
null
?
transparencyMode
.
name
()
:
FlutterView
.
TransparencyMode
.
transparent
.
name
());
args
.
putBoolean
(
ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY
,
shouldAttachEngineToActivity
);
return
args
;
}
/**
* Constructs a new {@code FlutterFragment} (or a subclass) that is configured based on
* properties set on this {@code CachedEngineFragmentBuilder}.
*/
@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.
...
...
@@ -494,6 +749,29 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
);
}
/**
* Returns the ID of a statically cached {@link FlutterEngine} to use within this
* {@code FlutterFragment}, or {@code null} if this {@code FlutterFragment} does not want to
* use a cached {@link FlutterEngine}.
*/
@Nullable
@Override
public
String
getCachedEngineId
()
{
return
getArguments
().
getString
(
ARG_CACHED_ENGINE_ID
,
null
);
}
/**
* Returns false if the {@link FlutterEngine} within this {@code FlutterFragment} should outlive
* the {@code FlutterFragment}, itself.
* <p>
* Defaults to true if no custom {@link FlutterEngine is provided}, false if a custom
* {@link FlutterEngine} is provided.
*/
@Override
public
boolean
shouldDestroyEngineWithHost
()
{
return
getArguments
().
getBoolean
(
ARG_DESTROY_ENGINE_WITH_FRAGMENT
,
false
);
}
/**
* Returns the name of the Dart method that this {@code FlutterFragment} should execute to
* start a Flutter app.
...
...
@@ -662,32 +940,16 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
}
/**
* See {@link Builder#shouldAttachEngineToActivity()}.
* See {@link NewEngineFragmentBuilder#shouldAttachEngineToActivity()} and
* {@link CachedEngineFragmentBuilder#shouldAttachEngineToActivity()}.
* <p>
* Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate
.Host
}
* Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate}
*/
@Override
public
boolean
shouldAttachEngineToActivity
()
{
return
getArguments
().
getBoolean
(
ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY
);
}
/**
* Returns true if the {@link FlutterEngine} within this {@code FlutterFragment} should outlive
* the {@code FlutterFragment}, itself.
* <p>
* Defaults to false. This method can be overridden in subclasses to retain the
* {@link FlutterEngine}.
* <p>
* Used by this {@code FlutterFragment}'s {@link FlutterActivityAndFragmentDelegate.Host}
*/
// TODO(mattcarroll): consider a dynamic determination of this preference based on whether the
// engine was created automatically, or if the engine was provided manually.
// Manually provided engines should probably not be destroyed.
@Override
public
boolean
retainFlutterEngineAfterHostDestruction
()
{
return
false
;
}
/**
* Invoked after the {@link FlutterView} within this {@code FlutterFragment} renders its first
* frame.
...
...
shell/platform/android/io/flutter/embedding/engine/FlutterEngineAndroidLifecycle.java
浏览文件 @
735255f3
// 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
;
...
...
shell/platform/android/io/flutter/embedding/engine/FlutterEngineCache.java
0 → 100644
浏览文件 @
735255f3
// 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
);
}
}
shell/platform/android/test/io/flutter/FlutterTestSuite.java
浏览文件 @
735255f3
...
...
@@ -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
{}
shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java
浏览文件 @
735255f3
...
...
@@ -5,7 +5,6 @@ import android.arch.lifecycle.Lifecycle;
import
android.content.Context
;
import
android.content.Intent
;
import
android.support.annotation.NonNull
;
import
android.support.annotation.Nullable
;
import
org.junit.After
;
import
org.junit.Before
;
...
...
@@ -17,6 +16,7 @@ import org.robolectric.RuntimeEnvironment;
import
org.robolectric.annotation.Config
;
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.plugins.activity.ActivityControlSurface
;
...
...
@@ -27,17 +27,16 @@ import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
import
io.flutter.embedding.engine.systemchannels.NavigationChannel
;
import
io.flutter.embedding.engine.systemchannels.SettingsChannel
;
import
io.flutter.embedding.engine.systemchannels.SystemChannel
;
import
io.flutter.plugin.platform.PlatformPlugin
;
import
io.flutter.plugin.platform.PlatformViewsController
;
import
io.flutter.view.FlutterMain
;
import
static
android
.
content
.
ComponentCallbacks2
.
TRIM_MEMORY_RUNNING_LOW
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertNull
;
import
static
org
.
mockito
.
Matchers
.
any
;
import
static
org
.
mockito
.
Matchers
.
eq
;
import
static
org
.
mockito
.
Mockito
.
mock
;
import
static
org
.
mockito
.
Mockito
.
never
;
import
static
org
.
mockito
.
Mockito
.
spy
;
import
static
org
.
mockito
.
Mockito
.
times
;
import
static
org
.
mockito
.
Mockito
.
verify
;
import
static
org
.
mockito
.
Mockito
.
when
;
...
...
@@ -46,8 +45,7 @@ import static org.mockito.Mockito.when;
@RunWith
(
RobolectricTestRunner
.
class
)
public
class
FlutterActivityAndFragmentDelegateTest
{
private
FlutterEngine
mockFlutterEngine
;
private
FakeHost
fakeHost
;
private
FakeHost
spyHost
;
private
FlutterActivityAndFragmentDelegate
.
Host
mockHost
;
@Before
public
void
setup
()
{
...
...
@@ -59,12 +57,20 @@ public class FlutterActivityAndFragmentDelegateTest {
// being tested.
mockFlutterEngine
=
mockFlutterEngine
();
// Create a fake Host, which is required by the delegate being tested.
fakeHost
=
new
FakeHost
();
fakeHost
.
flutterEngine
=
mockFlutterEngine
;
// Create a spy around the FakeHost so that we can verify method invocations.
spyHost
=
spy
(
fakeHost
);
// Create a mocked Host, which is required by the delegate being tested.
mockHost
=
mock
(
FlutterActivityAndFragmentDelegate
.
Host
.
class
);
when
(
mockHost
.
getContext
()).
thenReturn
(
RuntimeEnvironment
.
application
);
when
(
mockHost
.
getActivity
()).
thenReturn
(
Robolectric
.
setupActivity
(
Activity
.
class
));
when
(
mockHost
.
getLifecycle
()).
thenReturn
(
mock
(
Lifecycle
.
class
));
when
(
mockHost
.
getFlutterShellArgs
()).
thenReturn
(
new
FlutterShellArgs
(
new
String
[]{}));
when
(
mockHost
.
getDartEntrypointFunctionName
()).
thenReturn
(
"main"
);
when
(
mockHost
.
getAppBundlePath
()).
thenReturn
(
"/fake/path"
);
when
(
mockHost
.
getInitialRoute
()).
thenReturn
(
"/"
);
when
(
mockHost
.
getRenderMode
()).
thenReturn
(
FlutterView
.
RenderMode
.
surface
);
when
(
mockHost
.
getTransparencyMode
()).
thenReturn
(
FlutterView
.
TransparencyMode
.
transparent
);
when
(
mockHost
.
provideFlutterEngine
(
any
(
Context
.
class
))).
thenReturn
(
mockFlutterEngine
);
when
(
mockHost
.
shouldAttachEngineToActivity
()).
thenReturn
(
true
);
when
(
mockHost
.
shouldDestroyEngineWithHost
()).
thenReturn
(
true
);
}
@After
...
...
@@ -77,7 +83,7 @@ public class FlutterActivityAndFragmentDelegateTest {
public
void
itSendsLifecycleEventsToFlutter
()
{
// ---- Test setup ----
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
fake
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// We're testing lifecycle behaviors, which require/expect that certain methods have already
// been executed by the time they run. Therefore, we run those expected methods first.
...
...
@@ -117,41 +123,88 @@ public class FlutterActivityAndFragmentDelegateTest {
public
void
itDefersToTheHostToProvideFlutterEngine
()
{
// ---- Test setup ----
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
spy
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// --- Execute the behavior under test ---
// The FlutterEngine is created in onAttach().
delegate
.
onAttach
(
RuntimeEnvironment
.
application
);
// Verify that the host was asked to provide a FlutterEngine.
verify
(
spy
Host
,
times
(
1
)).
provideFlutterEngine
(
any
(
Context
.
class
));
verify
(
mock
Host
,
times
(
1
)).
provideFlutterEngine
(
any
(
Context
.
class
));
// Verify that the delegate's FlutterEngine is our mock FlutterEngine.
assertEquals
(
"The delegate failed to use the host's FlutterEngine."
,
mockFlutterEngine
,
delegate
.
getFlutterEngine
());
}
@Test
public
void
itUsesCachedEngineWhenProvided
()
{
// ---- Test setup ----
// Place a FlutterEngine in the static cache.
FlutterEngine
cachedEngine
=
mockFlutterEngine
();
FlutterEngineCache
.
getInstance
().
put
(
"my_flutter_engine"
,
cachedEngine
);
// Adjust fake host to request cached engine.
when
(
mockHost
.
getCachedEngineId
()).
thenReturn
(
"my_flutter_engine"
);
// 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
(
RuntimeEnvironment
.
application
);
delegate
.
onCreateView
(
null
,
null
,
null
);
delegate
.
onStart
();
delegate
.
onResume
();
// --- Verify that the cached engine was used ---
// Verify that the non-cached engine was not used.
verify
(
mockFlutterEngine
.
getDartExecutor
(),
never
()).
executeDartEntrypoint
(
any
(
DartExecutor
.
DartEntrypoint
.
class
));
// We should never instruct a cached engine to execute Dart code - it should already be executing it.
verify
(
cachedEngine
.
getDartExecutor
(),
never
()).
executeDartEntrypoint
(
any
(
DartExecutor
.
DartEntrypoint
.
class
));
// If the cached engine is being used, it should have sent a resumed lifecycle event.
verify
(
cachedEngine
.
getLifecycleChannel
(),
times
(
1
)).
appIsResumed
();
}
@Test
(
expected
=
IllegalStateException
.
class
)
public
void
itThrowsExceptionIfCachedEngineDoesNotExist
()
{
// ---- Test setup ----
// Adjust fake host to request cached engine that does not exist.
when
(
mockHost
.
getCachedEngineId
()).
thenReturn
(
"my_flutter_engine"
);
// 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
(
RuntimeEnvironment
.
application
);
// Expect IllegalStateException.
}
@Test
public
void
itGivesHostAnOpportunityToConfigureFlutterEngine
()
{
// ---- Test setup ----
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
spy
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// --- Execute the behavior under test ---
// The FlutterEngine is created in onAttach().
delegate
.
onAttach
(
RuntimeEnvironment
.
application
);
// Verify that the host was asked to configure our FlutterEngine.
verify
(
spy
Host
,
times
(
1
)).
configureFlutterEngine
(
mockFlutterEngine
);
verify
(
mock
Host
,
times
(
1
)).
configureFlutterEngine
(
mockFlutterEngine
);
}
@Test
public
void
itSendsInitialRouteToFlutter
()
{
// ---- Test setup ----
// Set initial route on our fake Host.
spyHost
.
initialRoute
=
"/my/route"
;
when
(
mockHost
.
getInitialRoute
()).
thenReturn
(
"/my/route"
)
;
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
spy
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// --- Execute the behavior under test ---
// The initial route is sent in onStart().
...
...
@@ -167,8 +220,8 @@ public class FlutterActivityAndFragmentDelegateTest {
public
void
itExecutesDartEntrypointProvidedByHost
()
{
// ---- Test setup ----
// Set Dart entrypoint parameters on fake host.
spyHost
.
appBundlePath
=
"/my/bundle/path"
;
spyHost
.
dartEntrypointFunctionName
=
"myEntrypoint"
;
when
(
mockHost
.
getAppBundlePath
()).
thenReturn
(
"/my/bundle/path"
)
;
when
(
mockHost
.
getDartEntrypointFunctionName
()).
thenReturn
(
"myEntrypoint"
)
;
// Create the DartEntrypoint that we expect to be executed.
DartExecutor
.
DartEntrypoint
dartEntrypoint
=
new
DartExecutor
.
DartEntrypoint
(
...
...
@@ -177,7 +230,7 @@ public class FlutterActivityAndFragmentDelegateTest {
);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
spy
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// --- Execute the behavior under test ---
// Dart is executed in onStart().
...
...
@@ -196,10 +249,10 @@ public class FlutterActivityAndFragmentDelegateTest {
public
void
itAttachesFlutterToTheActivityIfDesired
()
{
// ---- Test setup ----
// Declare that the host wants Flutter to attach to the surrounding Activity.
spyHost
.
shouldAttachToActivity
=
true
;
when
(
mockHost
.
shouldAttachEngineToActivity
()).
thenReturn
(
true
)
;
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
spy
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// --- Execute the behavior under test ---
// Flutter is attached to the surrounding Activity in onAttach.
...
...
@@ -222,10 +275,10 @@ public class FlutterActivityAndFragmentDelegateTest {
public
void
itDoesNotAttachFlutterToTheActivityIfNotDesired
()
{
// ---- Test setup ----
// Declare that the host does NOT want Flutter to attach to the surrounding Activity.
spyHost
.
shouldAttachToActivity
=
false
;
when
(
mockHost
.
shouldAttachEngineToActivity
()).
thenReturn
(
false
)
;
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
spy
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// --- Execute the behavior under test ---
// Flutter is attached to the surrounding Activity in onAttach.
...
...
@@ -244,7 +297,7 @@ public class FlutterActivityAndFragmentDelegateTest {
@Test
public
void
itSendsPopRouteMessageToFlutterWhenHardwareBackButtonIsPressed
()
{
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
spy
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// --- Execute the behavior under test ---
// The FlutterEngine is setup in onAttach().
...
...
@@ -260,7 +313,7 @@ public class FlutterActivityAndFragmentDelegateTest {
@Test
public
void
itForwardsOnRequestPermissionsResultToFlutterEngine
()
{
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
spy
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// --- Execute the behavior under test ---
// The FlutterEngine is setup in onAttach().
...
...
@@ -276,7 +329,7 @@ public class FlutterActivityAndFragmentDelegateTest {
@Test
public
void
itForwardsOnNewIntentToFlutterEngine
()
{
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
spy
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// --- Execute the behavior under test ---
// The FlutterEngine is setup in onAttach().
...
...
@@ -292,7 +345,7 @@ public class FlutterActivityAndFragmentDelegateTest {
@Test
public
void
itForwardsOnActivityResultToFlutterEngine
()
{
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
spy
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// --- Execute the behavior under test ---
// The FlutterEngine is setup in onAttach().
...
...
@@ -308,7 +361,7 @@ public class FlutterActivityAndFragmentDelegateTest {
@Test
public
void
itForwardsOnUserLeaveHintToFlutterEngine
()
{
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
spy
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// --- Execute the behavior under test ---
// The FlutterEngine is setup in onAttach().
...
...
@@ -324,7 +377,7 @@ public class FlutterActivityAndFragmentDelegateTest {
@Test
public
void
itSendsMessageOverSystemChannelWhenToldToTrimMemory
()
{
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
spy
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// --- Execute the behavior under test ---
// The FlutterEngine is setup in onAttach().
...
...
@@ -340,7 +393,7 @@ public class FlutterActivityAndFragmentDelegateTest {
@Test
public
void
itSendsMessageOverSystemChannelWhenInformedOfLowMemory
()
{
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
spy
Host
);
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mock
Host
);
// --- Execute the behavior under test ---
// The FlutterEngine is setup in onAttach().
...
...
@@ -353,6 +406,117 @@ public class FlutterActivityAndFragmentDelegateTest {
verify
(
mockFlutterEngine
.
getSystemChannel
(),
times
(
1
)).
sendMemoryPressureWarning
();
}
@Test
public
void
itDestroysItsOwnEngineIfHostRequestsIt
()
{
// ---- Test setup ----
// Adjust fake host to request engine destruction.
when
(
mockHost
.
shouldDestroyEngineWithHost
()).
thenReturn
(
true
);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mockHost
);
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate
.
onAttach
(
RuntimeEnvironment
.
application
);
delegate
.
onCreateView
(
null
,
null
,
null
);
delegate
.
onStart
();
delegate
.
onResume
();
delegate
.
onPause
();
delegate
.
onStop
();
delegate
.
onDestroyView
();
delegate
.
onDetach
();
// --- Verify that the cached engine was destroyed ---
verify
(
mockFlutterEngine
,
times
(
1
)).
destroy
();
}
@Test
public
void
itDoesNotDestroyItsOwnEngineWhenHostSaysNotTo
()
{
// ---- Test setup ----
// Adjust fake host to request engine destruction.
when
(
mockHost
.
shouldDestroyEngineWithHost
()).
thenReturn
(
false
);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mockHost
);
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate
.
onAttach
(
RuntimeEnvironment
.
application
);
delegate
.
onCreateView
(
null
,
null
,
null
);
delegate
.
onStart
();
delegate
.
onResume
();
delegate
.
onPause
();
delegate
.
onStop
();
delegate
.
onDestroyView
();
delegate
.
onDetach
();
// --- Verify that the cached engine was destroyed ---
verify
(
mockFlutterEngine
,
never
()).
destroy
();
}
@Test
public
void
itDestroysCachedEngineWhenHostRequestsIt
()
{
// ---- Test setup ----
// Place a FlutterEngine in the static cache.
FlutterEngine
cachedEngine
=
mockFlutterEngine
();
FlutterEngineCache
.
getInstance
().
put
(
"my_flutter_engine"
,
cachedEngine
);
// Adjust fake host to request cached engine.
when
(
mockHost
.
getCachedEngineId
()).
thenReturn
(
"my_flutter_engine"
);
// Adjust fake host to request engine destruction.
when
(
mockHost
.
shouldDestroyEngineWithHost
()).
thenReturn
(
true
);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mockHost
);
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate
.
onAttach
(
RuntimeEnvironment
.
application
);
delegate
.
onCreateView
(
null
,
null
,
null
);
delegate
.
onStart
();
delegate
.
onResume
();
delegate
.
onPause
();
delegate
.
onStop
();
delegate
.
onDestroyView
();
delegate
.
onDetach
();
// --- Verify that the cached engine was destroyed ---
verify
(
cachedEngine
,
times
(
1
)).
destroy
();
assertNull
(
FlutterEngineCache
.
getInstance
().
get
(
"my_flutter_engine"
));
}
@Test
public
void
itDoesNotDestroyCachedEngineWhenHostSaysNotTo
()
{
// ---- Test setup ----
// Place a FlutterEngine in the static cache.
FlutterEngine
cachedEngine
=
mockFlutterEngine
();
FlutterEngineCache
.
getInstance
().
put
(
"my_flutter_engine"
,
cachedEngine
);
// Adjust fake host to request cached engine.
when
(
mockHost
.
getCachedEngineId
()).
thenReturn
(
"my_flutter_engine"
);
// Adjust fake host to request engine retention.
when
(
mockHost
.
shouldDestroyEngineWithHost
()).
thenReturn
(
false
);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate
delegate
=
new
FlutterActivityAndFragmentDelegate
(
mockHost
);
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate
.
onAttach
(
RuntimeEnvironment
.
application
);
delegate
.
onCreateView
(
null
,
null
,
null
);
delegate
.
onStart
();
delegate
.
onResume
();
delegate
.
onPause
();
delegate
.
onStop
();
delegate
.
onDestroyView
();
delegate
.
onDetach
();
// --- Verify that the cached engine was NOT destroyed ---
verify
(
cachedEngine
,
never
()).
destroy
();
}
/**
* Creates a mock {@link FlutterEngine}.
* <p>
...
...
@@ -387,115 +551,4 @@ public class FlutterActivityAndFragmentDelegateTest {
return
engine
;
}
/**
* A {@link FlutterActivityAndFragmentDelegate.Host} that returns values desired by this
* test suite.
* <p>
* Sane defaults are set for all properties. Tests in this suite can alter {@code FakeHost}
* properties as needed for each test.
*/
private
static
class
FakeHost
implements
FlutterActivityAndFragmentDelegate
.
Host
{
private
FlutterEngine
flutterEngine
;
private
String
initialRoute
=
null
;
private
String
appBundlePath
=
"fake/path/"
;
private
String
dartEntrypointFunctionName
=
"main"
;
private
Activity
activity
;
private
boolean
shouldAttachToActivity
=
false
;
private
boolean
retainFlutterEngine
=
false
;
@NonNull
@Override
public
Context
getContext
()
{
return
RuntimeEnvironment
.
application
;
}
@Nullable
@Override
public
Activity
getActivity
()
{
if
(
activity
==
null
)
{
// We must provide a real (or close to real) Activity because it is passed to
// the FlutterView that the delegate instantiates.
activity
=
Robolectric
.
setupActivity
(
Activity
.
class
);
}
return
activity
;
}
@NonNull
@Override
public
Lifecycle
getLifecycle
()
{
return
mock
(
Lifecycle
.
class
);
}
@NonNull
@Override
public
FlutterShellArgs
getFlutterShellArgs
()
{
return
new
FlutterShellArgs
(
new
String
[]{});
}
@NonNull
@Override
public
String
getDartEntrypointFunctionName
()
{
return
dartEntrypointFunctionName
;
}
@NonNull
@Override
public
String
getAppBundlePath
()
{
return
appBundlePath
;
}
@Nullable
@Override
public
String
getInitialRoute
()
{
return
initialRoute
;
}
@NonNull
@Override
public
FlutterView
.
RenderMode
getRenderMode
()
{
return
FlutterView
.
RenderMode
.
surface
;
}
@NonNull
@Override
public
FlutterView
.
TransparencyMode
getTransparencyMode
()
{
return
FlutterView
.
TransparencyMode
.
opaque
;
}
@Nullable
@Override
public
SplashScreen
provideSplashScreen
()
{
return
null
;
}
@Nullable
@Override
public
FlutterEngine
provideFlutterEngine
(
@NonNull
Context
context
)
{
return
flutterEngine
;
}
@Nullable
@Override
public
PlatformPlugin
providePlatformPlugin
(
@Nullable
Activity
activity
,
@NonNull
FlutterEngine
flutterEngine
)
{
return
null
;
}
@Override
public
void
configureFlutterEngine
(
@NonNull
FlutterEngine
flutterEngine
)
{}
@Override
public
boolean
shouldAttachEngineToActivity
()
{
return
shouldAttachToActivity
;
}
@Override
public
boolean
retainFlutterEngineAfterHostDestruction
()
{
return
retainFlutterEngine
;
}
@Override
public
void
onFirstFrameRendered
()
{}
}
}
shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java
0 → 100644
浏览文件 @
735255f3
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
());
}
}
shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java
0 → 100644
浏览文件 @
735255f3
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
());
}
}
shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineCacheTest.java
0 → 100644
浏览文件 @
735255f3
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.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录