将 Flutter Fragment 添加到 Android 应用
了解如何将 Flutter Fragment 添加到您现有的 Android 应用。
本指南介绍了如何将 Flutter Fragment 添加到现有的 Android 应用。在 Android 中,Fragment 代表更大的 UI 的模块化部分。Fragment 可用于呈现滑动抽屉、选项卡内容、ViewPager 中的页面,或者它可能只是单 Activity 应用中的普通屏幕。Flutter 提供了一个 FlutterFragment,以便开发者可以在可以使用常规 Fragment 的任何地方呈现 Flutter 体验。
如果 Activity 对您的应用程序需求同样适用,请考虑 使用 FlutterActivity 而不是 FlutterFragment,后者使用起来更快更简单。
FlutterFragment 允许开发者控制 Fragment 内 Flutter 体验的以下细节
- 初始 Flutter 路由
- 要执行的 Dart 入口点
- 不透明与半透明背景
FlutterFragment是否应控制其周围的Activity- 是否应使用新的
FlutterEngine或缓存的FlutterEngine
FlutterFragment 还附带一些必须从其周围的 Activity 转发的调用。这些调用允许 Flutter 适当地响应操作系统事件。
本指南介绍了所有类型的 FlutterFragment 及其要求。
将 FlutterFragment 添加到带有新 FlutterEngine 的 Activity
#
使用 FlutterFragment 的第一件事是将它添加到宿主 Activity 中。
要将 FlutterFragment 添加到宿主 Activity,请在 Activity 的 onCreate() 中实例化并附加 FlutterFragment 的实例,或者在对您的应用有用的其他时间。
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();
}
}
}
前面的代码足以渲染一个 Flutter UI,该 UI 从对您的 main() Dart 入口点的调用开始,初始 Flutter 路由为 /,以及一个新的 FlutterEngine。但是,这段代码不足以实现所有预期的 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);
}
}
将操作系统信号转发到 Flutter 后,您的 FlutterFragment 就能正常工作。现在,您已将 FlutterFragment 添加到现有的 Android 应用中。
最简单的集成路径使用新的 FlutterEngine,这需要非同寻常的初始化时间,导致第一次初始化和渲染 Flutter 时 UI 为空白。通过使用缓存的、预热的 FlutterEngine,可以避免大部分时间开销,接下来将讨论该方法。
使用预热的 FlutterEngine
#
默认情况下,FlutterFragment 会创建自己的 FlutterEngine 实例,这需要非同寻常的预热时间。这意味着您的用户会看到一个空白 Fragment 片刻。您可以通过使用现有的、预热的 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,如前所示,您的应用会尽快渲染第一个 Flutter 帧。
缓存引擎的初始路由
#初始路由的概念在配置 FlutterActivity 或带有新 FlutterEngine 的 FlutterFragment 时可用。但是,当使用缓存引擎时,FlutterActivity 和 FlutterFragment 不提供初始路由的概念。这是因为缓存引擎预计已经正在运行 Dart 代码,这意味着配置初始路由为时已晚。
希望其缓存引擎从自定义初始路由开始的开发者可以在执行 Dart 入口点之前配置其缓存的 FlutterEngine 以使用自定义初始路由。以下示例演示了使用缓存引擎的初始路由
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);
}
}
通过设置导航通道的初始路由,关联的 FlutterEngine 将在初始执行 runApp() Dart 函数时显示所需的路由。
在初始执行 runApp() 之后更改导航通道的初始路由属性无效。希望在不同的 Activity 和 Fragment 之间使用相同的 FlutterEngine 并在这两个显示之间切换路由的开发者需要设置方法通道并显式指示他们的 Dart 代码更改 Navigator 路由。
显示启动画面
#首次显示 Flutter 内容需要一些等待时间,即使使用了预热的 FlutterEngine。为了帮助改善用户在此简短等待期间的体验,Flutter 支持显示启动画面(也称为“启动屏幕”)直到 Flutter 渲染其第一个帧。有关如何显示启动屏幕的说明,请参阅 启动画面指南。
使用指定的初始路由运行 Flutter
#Android 应用可能包含许多独立的 Flutter 体验,在不同的 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 应用中,只有一个 Dart 入口点:main(),但您可以定义其他入口点。
FlutterFragment 支持指定要为给定的 Flutter 体验执行的所需的 Dart 入口点。要指定入口点,请构建 FlutterFragment,如下所示
val flutterFragment = FlutterFragment.withNewEngine()
.dartEntrypoint("mySpecialEntrypoint")
.build()
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.dartEntrypoint("mySpecialEntrypoint")
.build();
FlutterFragment 配置的结果是执行名为 mySpecialEntrypoint() 的 Dart 入口点。请注意,括号 () 不包含在 dartEntrypoint String 名称中。
控制 FlutterFragment 的渲染模式
#
FlutterFragment 可以使用 SurfaceView 渲染其 Flutter 内容,也可以使用 TextureView。默认值为 SurfaceView,其性能比 TextureView 好得多。但是,SurfaceView 不能插入到 Android View 层次结构的中间。SurfaceView 必须是层次结构中最底层的 View,或者层次结构中最顶层的 View。此外,在 Android N 之前的版本上,由于其布局和渲染与 View 层次结构的其余部分不同步,因此无法对 SurfaceView 进行动画处理。如果您的应用需要这两种用例,则需要使用 TextureView 代替 SurfaceView。通过使用 texture RenderMode 构建 FlutterFragment 来选择 TextureView
// 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 绘制的任何像素,该背景为黑色。出于性能原因,使用不透明背景的渲染是首选的渲染模式。在 Android 上使用透明度进行 Flutter 渲染会对性能产生负面影响。但是,许多设计需要 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 的状态栏、导航栏和方向等系统 chrome 是合理的。
在其他应用中,Fragment 用于表示 UI 的一部分。FlutterFragment 可用于实现抽屉内部、视频播放器或单个卡片。在这种情况下,FlutterFragment 不应影响 Android 的系统 chrome,因为同一 Window 中还有其他 UI 部分。
FlutterFragment 包含一个概念,有助于区分 FlutterFragment 应该能够控制其宿主 Activity 的情况,以及 FlutterFragment 仅应影响其自身行为的情况。要防止 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 传递给 shouldAttachEngineToActivity() Builder 方法可防止 Flutter 与周围的 Activity 交互。默认值为 true,允许 Flutter 和 Flutter 插件与周围的 Activity 交互。