向 Android 应用中添加 Flutter Fragment
使用新的 FlutterEngine 向 Activity 中添加 FlutterFragment 使用预热的 FlutterEngine 展示闪屏页 指定 Flutter 运行的初始路由 指定 Flutter 运行的入口 控制 FlutterFragment 的渲染模式 展示透明的 FlutterFragment FlutterFragment 与其 Activity 之间的关系
本篇指南介绍如何向一个现有的 Android 应用中添加 Flutter Fragment
。在 Android 开发中,一个 Fragment
代表了一块较大的模块化 UI。
Fragment
可能被用来展示滑动抽屉、标签内容和 ViewPager
中的页面,或者在单 Activity
应用中,Fragment
可能仅代表正常的屏幕内容。
Flutter 提供了FlutterFragment
,以便于开发者们可以在任何使用常规 Fragment
的地方呈现 Flutter 的内容。
如果 Activity
同样适用于你的应用需求,可以考虑 使用 FlutterActivity
而非 FlutterFragment
,前者更加快捷易用。
FlutterFragment
允许开发者在 Fragment
中控制以下 Flutter 的开发细节:
-
Flutter 初始路由
-
将要执行的 Dart 入口
-
非透明或者透明的背景
-
FlutterFragment
是否能控制它外层的Activity
-
使用新的还是缓存的
FlutterEngine
FlutterFragment
还提供了一些回调事件,这些回调必须由它所在的 Activity
触发执行。这些回调允许 Flutter 适时地响应一些系统事件。
这篇指南介绍了 FlutterFragment
的所有使用方式和使用要求。
使用新的 FlutterEngine
向 Activity
中添加 FlutterFragment
#
使用 FlutterFragment
的第一步是将其添加进宿主 Activity
。
要向宿主 Activity
中添加 FlutterFragment
,需要在 Activity
的 onCreate()
或者其它合适的地方,实例化 FlutterFragment
并且与 Activity
绑定。
class MyActivity : FragmentActivity() {
companion object {
// Define a tag String to represent the FlutterFragment within this
// Activity's FragmentManager. This value can be whatever you'd like.
private const val TAG_FLUTTER_FRAGMENT = "flutter_fragment"
}
// Declare a local variable to reference the FlutterFragment so that you
// can forward calls to it later.
private var flutterFragment: FlutterFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Inflate a layout that has a container for your FlutterFragment. For
// this example, assume that a FrameLayout exists with an ID of
// R.id.fragment_container.
setContentView(R.layout.my_activity_layout)
// Get a reference to the Activity's FragmentManager to add a new
// FlutterFragment, or find an existing one.
val fragmentManager: FragmentManager = supportFragmentManager
// Attempt to find an existing FlutterFragment, in case this is not the
// first time that onCreate() was run.
flutterFragment = fragmentManager
.findFragmentByTag(TAG_FLUTTER_FRAGMENT) as FlutterFragment?
// Create and attach a FlutterFragment if one does not exist.
if (flutterFragment == null) {
var newFlutterFragment = FlutterFragment.createDefault()
flutterFragment = newFlutterFragment
fragmentManager
.beginTransaction()
.add(
R.id.fragment_container,
newFlutterFragment,
TAG_FLUTTER_FRAGMENT
)
.commit()
}
}
}
public class MyActivity extends FragmentActivity {
// Define a tag String to represent the FlutterFragment within this
// Activity's FragmentManager. This value can be whatever you'd like.
private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment";
// Declare a local variable to reference the FlutterFragment so that you
// can forward calls to it later.
private FlutterFragment flutterFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate a layout that has a container for your FlutterFragment.
// For this example, assume that a FrameLayout exists with an ID of
// R.id.fragment_container.
setContentView(R.layout.my_activity_layout);
// Get a reference to the Activity's FragmentManager to add a new
// FlutterFragment, or find an existing one.
FragmentManager fragmentManager = getSupportFragmentManager();
// Attempt to find an existing FlutterFragment,
// in case this is not the first time that onCreate() was run.
flutterFragment = (FlutterFragment) fragmentManager
.findFragmentByTag(TAG_FLUTTER_FRAGMENT);
// Create and attach a FlutterFragment if one does not exist.
if (flutterFragment == null) {
flutterFragment = FlutterFragment.createDefault();
fragmentManager
.beginTransaction()
.add(
R.id.fragment_container,
flutterFragment,
TAG_FLUTTER_FRAGMENT
)
.commit();
}
}
}
上面的代码会以 main()
为 Dart 入口函数, /
为初始路由,并使用新的 FlutterEngine
,能够正确渲染出 Flutter UI。但是,这些代码还无法使 Flutter 如预期一样完全正常地工作。
Flutter 依赖操作系统的各种信号,这些信号必须通过宿主 Activity
发送到 FlutterFragment
中。下面的示例展示了这些系统回调:
class MyActivity : FragmentActivity() {
override fun onPostResume() {
super.onPostResume()
flutterFragment!!.onPostResume()
}
override fun onNewIntent(@NonNull intent: Intent) {
flutterFragment!!.onNewIntent(intent)
}
override fun onBackPressed() {
flutterFragment!!.onBackPressed()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String?>,
grantResults: IntArray
) {
flutterFragment!!.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
)
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?
) {
super.onActivityResult(requestCode, resultCode, data)
flutterFragment!!.onActivityResult(
requestCode,
resultCode,
data
)
}
override fun onUserLeaveHint() {
flutterFragment!!.onUserLeaveHint()
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
flutterFragment!!.onTrimMemory(level)
}
}
public class MyActivity extends FragmentActivity {
@Override
public void onPostResume() {
super.onPostResume();
flutterFragment.onPostResume();
}
@Override
protected void onNewIntent(@NonNull Intent intent) {
flutterFragment.onNewIntent(intent);
}
@Override
public void onBackPressed() {
flutterFragment.onBackPressed();
}
@Override
public void onRequestPermissionsResult(
int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults
) {
flutterFragment.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
);
}
@Override
public void onActivityResult(
int requestCode,
int resultCode,
@Nullable Intent data
) {
super.onActivityResult(requestCode, resultCode, data);
flutterFragment.onActivityResult(
requestCode,
resultCode,
data
);
}
@Override
public void onUserLeaveHint() {
flutterFragment.onUserLeaveHint();
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
flutterFragment.onTrimMemory(level);
}
}
随着 OS 信号传递到 Flutter,你的 FlutterFragment
可以如预期正常工作。现在可以尝试将 FlutterFragment
添加进你的 Android 应用了。
使用新的 FlutterEngine
是最简单的集成方式,但是会存在一段明显的初始化时间,此时,在 Flutter 初始化和首次渲染完成之前会出现短暂的白屏。使用缓存、预热的 FlutterEngine
则可以避免上述的大部分耗时,下面我们将讨论这些内容。
使用预热的 FlutterEngine
#
默认情况下,FlutterFragment
会创建它自己的 FlutterEngine
实例,同时也需要不少的启动时间。这就意味着你的用户会看到短暂的白屏。通过使用已存在的、预热的 FlutterEngine
就可以大幅度减少启动的耗时。
要在 FlutterFragment
中使用预热 FlutterEngine
,可以使用工厂方法 withCachedEngine()
实例化 FlutterFragment
。
// Somewhere in your app, before your FlutterFragment is needed,
// like in the Application class ...
// Instantiate a FlutterEngine.
val flutterEngine = FlutterEngine(context)
// Start executing Dart code in the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault()
)
// Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine)
FlutterFragment.withCachedEngine("my_engine_id").build()
// Somewhere in your app, before your FlutterFragment is needed,
// like in the Application class ...
// Instantiate a FlutterEngine.
FlutterEngine flutterEngine = new FlutterEngine(context);
// Start executing Dart code in the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault()
);
// Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine);
FlutterFragment.withCachedEngine("my_engine_id").build();
FlutterFragment
内部可访问 FlutterEngineCache
,并且可以根据传递给 withCachedEngine()
的 ID 获取预热的 FlutterEngine
。
如上所示,通过提供预热的 FlutterEngine
,你的应用将以最快速度渲染出第一帧。
缓存引擎中的初始路由
#The concept of an initial route is available when configuring a
FlutterActivity
or a FlutterFragment
with a new FlutterEngine
.
However, FlutterActivity
and FlutterFragment
don't offer the
concept of an initial route when using a cached engine.
This is because a cached engine is expected to already be
running Dart code, which means it's too late to configure the
initial route.
Developers that would like their cached engine to begin
with a custom initial route can configure their cached
FlutterEngine
to use a custom initial route just before
executing the Dart entrypoint. The following example
demonstrates the use of an initial route with a cached engine:
class MyApplication : Application() {
lateinit var flutterEngine : FlutterEngine
override fun onCreate() {
super.onCreate()
// Instantiate a FlutterEngine.
flutterEngine = FlutterEngine(this)
// Configure an initial route.
flutterEngine.navigationChannel.setInitialRoute("your/route/here");
// Start executing Dart code to pre-warm the FlutterEngine.
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
// Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine)
}
}
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Instantiate a FlutterEngine.
flutterEngine = new FlutterEngine(this);
// Configure an initial route.
flutterEngine.getNavigationChannel().setInitialRoute("your/route/here");
// Start executing Dart code to pre-warm the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault()
);
// Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine);
}
}
By setting the initial route of the navigation channel, the associated
FlutterEngine
displays the desired route upon initial execution of the
runApp()
Dart function.
Changing the initial route property of the navigation channel
after the initial execution of runApp()
has no effect.
Developers who would like to use the same FlutterEngine
between different Activity
s and Fragment
s and switch
the route between those displays need to set up a method channel and
explicitly instruct their Dart code to change Navigator
routes.
展示闪屏页
#即使使用了预热的 FlutterEngine
,第一次展示 Flutter 的内容仍然需要一些时间。为了更进一步提升用户体验,Flutter 支持在第一帧渲染完成之前展示闪屏页。关于如何展示闪屏页的详细说明,请参阅这篇 闪屏页指南。
指定 Flutter 运行的初始路由
#一个 Android 应用中可能包含很多独立的 Flutter 界面,这些界面显示在不同的 FlutterFragment
上,每个 FlutterFragment
的 FlutterEngine
也是独立的。在这些情况下,每个 Flutter 界面通过不同的初始路由(除 /
以外的路由)启动是很正常的。为此,FlutterFragment
的 Builder
允许指定一个你希望的初始路由,如下所示:
// With a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
.initialRoute("myInitialRoute/")
.build()
// With a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.initialRoute("myInitialRoute/")
.build();
指定 Flutter 运行的入口
#和变化的初始路由类似,不同的 FlutterFragment
可能需要执行不同的 Dart 代码入口。正常的 Flutter 应用中,只会有一个 main()
入口,但是你也可以定义不同的入口。
FlutterFragment
支持指定需要的 Dart 入口以运行对应的 Flutter 界面。下面的代码展示了如何在构建 FlutterFragment
时指定一个入口。
val flutterFragment = FlutterFragment.withNewEngine()
.dartEntrypoint("mySpecialEntrypoint")
.build()
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.dartEntrypoint("mySpecialEntrypoint")
.build();
这里,FlutterFragment
的配置会将 Dart 入口的执行函数设置为 mySpecialEntrypoint()
。需要注意的是,括号 ()
不包含在 dartEntrypoint
的 String
类型的参数中。
控制 FlutterFragment
的渲染模式
#
FlutterFragment
可以选择使用 SurfaceView
或者 TextureView
来渲染其内容。默认配置的 SurfaceView
在性能上明显好于 TextureView
。然而,SurfaceView
无法插入到 Android 的 View
层级之中。
SurfaceView
在视图层级中必须是最底层的 View
或者最顶层的 View
。此外,在 Android N 之前,SurfaceView
无法用于制作动画,因为它们的布局和渲染无法和视图层级中的其它 View
同步。如果上述这些用例之一在你的应用需求之中,你需要使用 TextureView
替换 SurfaceView
。要选择 TextureView
,可以在构建 FlutterFragment
时指定 RenderMode
为 texture
:
// With a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
.renderMode(FlutterView.RenderMode.texture)
.build()
// With a cached FlutterEngine.
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.renderMode(FlutterView.RenderMode.texture)
.build()
// With a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.renderMode(FlutterView.RenderMode.texture)
.build();
// With a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.renderMode(FlutterView.RenderMode.texture)
.build();
使用上面展示的代码配置,FlutterFragment
可以将它的 UI 渲染为 TextureView
。
展示透明的 FlutterFragment
#
默认情况下,FlutterFragment
使用 SurfaceView
渲染且背景不透明。(参考「控制 FlutterFragment
的渲染模式」)任何未经 Flutter 绘制的像素在背景中都是黑色的。出于性能方面的考虑,我们优先选择使用不透明的背景进行渲染。渲染透明的 Flutter 界面在 Android 平台上会产生性能方面的负面影响。但是许多设计都需要 Flutter 界面中包含透明的像素以显示底层的 Android UI。因此,Flutter 支持 FlutterFragment
半透明。
要启动一个透明的 FlutterFragment
,可以使用以下方式进行构建:
// Using a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
.transparencyMode(FlutterView.TransparencyMode.transparent)
.build()
// Using a cached FlutterEngine.
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.transparencyMode(FlutterView.TransparencyMode.transparent)
.build()
// Using a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.transparencyMode(FlutterView.TransparencyMode.transparent)
.build();
// Using a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.transparencyMode(FlutterView.TransparencyMode.transparent)
.build();
FlutterFragment
与其 Activity
之间的关系
#
一些应用选择使用 Fragment
作为整个 Android 屏幕内容。在这些应用里,Fragment
可能会需要控制一些系统属性,例如 Android 的状态栏、导航栏以及屏幕方向。
在其它应用中,Fragment
通常只是整个 UI 的一部分。
FlutterFragment
可能用于实现抽屉、视频播放器或卡片的内容。在这些情况下,FlutterFragment
就不应当影响 Android 的系统属性,因为同一个 Window
中还有其它 UI 组件。
FlutterFragment
自身包含一种特性,可以用于决定 FlutterFragment
是否应该控制宿主 Activity
,或者只影响自身行为。要预防 FlutterFragment
将其 Activity
暴露给 Flutter 插件,以免 Flutter 控制 Activity
的系统 UI,可以使用 FlutterFragment
的 Builder
中的 shouldAttachEngineToActivity()
方法。如下所示:
// Using a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
.shouldAttachEngineToActivity(false)
.build()
// Using a cached FlutterEngine.
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.shouldAttachEngineToActivity(false)
.build()
// Using a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.shouldAttachEngineToActivity(false)
.build();
// Using a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.shouldAttachEngineToActivity(false)
.build();
传递 false
给 Builder
的 shouldAttachEngineToActivity()
方法,可防止 Flutter 与所属的 Activity
交互。默认值为 true
,此时允许 Flutter 和 Flutter 插件与 Activity
交互。
除非另有说明,本文档之所提及适用于 Flutter 的最新稳定版本,本页面最后更新时间: 2024-07-03。 查看文档源码 或者 为本页面内容提出建议。