将 Flutter 屏幕添加到 Android 应用程序

本指南介绍如何将单个 Flutter 屏幕添加到现有的 Android 应用程序。Flutter 屏幕可以作为普通的不透明屏幕添加,也可以作为透明的半透明屏幕添加。本指南中介绍了这两种选择。

添加一个正常的 Flutter 屏幕

Add Flutter Screen Header

步骤 1:将 FlutterActivity 添加到 AndroidManifest.xml

Flutter 提供 FlutterActivity 在 Android 应用程序中显示 Flutter 体验。与任何其他 Activity 一样,FlutterActivity 必须在 AndroidManifest.xml 中注册。将以下 XML 添加到 application 标记下的 AndroidManifest.xml 文件中

<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/LaunchTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  />

@style/LaunchTheme 的引用可以替换为任何要应用于 FlutterActivity 的 Android 主题。主题的选择决定了应用于 Android 系统边框的颜色,例如 Android 的导航栏,以及在 Flutter UI 首次呈现自身之前 FlutterActivity 的背景色。

步骤 2:启动 FlutterActivity

在清单文件中注册 FlutterActivity 后,添加代码以从应用程序中您希望的任何点启动 FlutterActivity。以下示例显示了从 OnClickListener 启动 FlutterActivity

ExistingActivity.java
myButton.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity.createDefaultIntent(currentActivity)
    );
  }
});
ExistingActivity.kt
myButton.setOnClickListener {
  startActivity(
    FlutterActivity.createDefaultIntent(this)
  )
}

前面的示例假定您的 Dart 入口点称为 main(),并且您的初始 Flutter 路由为“/”。无法使用 Intent 更改 Dart 入口点,但可以使用 Intent 更改初始路由。以下示例演示如何启动一个 FlutterActivity,该活动最初在 Flutter 中呈现自定义路由。

ExistingActivity.java
myButton.addOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity
        .withNewEngine()
        .initialRoute("/my_route")
        .build(currentActivity)
      );
  }
});
ExistingActivity.kt
myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withNewEngine()
      .initialRoute("/my_route")
      .build(this)
  )
}

"/my_route" 替换为您所需的初始路由。

使用 withNewEngine() 工厂方法可以配置 FlutterActivity,它会在内部创建自己的 FlutterEngine 实例。这会带来不小的初始化时间。另一种方法是指示 FlutterActivity 使用预热缓存的 FlutterEngine,这可以最大程度缩短 Flutter 的初始化时间。接下来将讨论这种方法。

步骤 3:(可选) 使用缓存的 FlutterEngine

默认情况下,每个 FlutterActivity 都会创建自己的 FlutterEngine。每个 FlutterEngine 都具有不小的预热时间。这意味着启动标准 FlutterActivity 会在您的 Flutter 体验可见之前带来短暂延迟。为了最大程度缩短此延迟,您可以在到达 FlutterActivity 之前预热 FlutterEngine,然后可以使用预热的 FlutterEngine

要预热 FlutterEngine,请在您的应用中找到一个合适的位置来实例化 FlutterEngine。以下示例在 Application 类中预热 FlutterEngine

MyApplication.java
public class MyApplication extends Application {
  public FlutterEngine flutterEngine;
  
  @Override
  public void onCreate() {
    super.onCreate();
    // Instantiate a FlutterEngine.
    flutterEngine = new FlutterEngine(this);

    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.getDartExecutor().executeDartEntrypoint(
      DartEntrypoint.createDefault()
    );

    // Cache the FlutterEngine to be used by FlutterActivity.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine);
  }
}
MyApplication.kt
class MyApplication : Application() {
  lateinit var flutterEngine : FlutterEngine

  override fun onCreate() {
    super.onCreate()

    // Instantiate a FlutterEngine.
    flutterEngine = FlutterEngine(this)

    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.dartExecutor.executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    )

    // Cache the FlutterEngine to be used by FlutterActivity.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine)
  }
}

传递给 FlutterEngineCache 的 ID 可以是您想要的任何内容。请确保将相同的 ID 传递给应使用缓存的 FlutterEngine 的任何 FlutterActivityFlutterFragment。接下来将讨论使用缓存的 FlutterEngineFlutterActivity

使用预热缓存的 FlutterEngine,你现在需要指示你的 FlutterActivity 使用缓存的 FlutterEngine,而不是创建一个新的。要实现此目的,请使用 FlutterActivitywithCachedEngine() 构建器

ExistingActivity.java
myButton.addOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity
        .withCachedEngine("my_engine_id")
        .build(currentActivity)
      );
  }
});
ExistingActivity.kt
myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withCachedEngine("my_engine_id")
      .build(this)
  )
}

使用 withCachedEngine() 工厂方法时,传递与缓存所需的 FlutterEngine 时使用的相同的 ID。

现在,当你启动 FlutterActivity 时,Flutter 内容的显示延迟会大大减少。

使用缓存引擎的初始路由

在使用新的 FlutterEngine 配置 FlutterActivityFlutterFragment 时,可以使用初始路由的概念。但是,当使用缓存引擎时,FlutterActivityFlutterFragment 不提供初始路由的概念。这是因为缓存引擎预计已经运行 Dart 代码,这意味着配置初始路由为时已晚。

希望缓存引擎从自定义初始路由开始的开发者可以在执行 Dart 入口点之前,将缓存的 FlutterEngine 配置为使用自定义初始路由。以下示例演示了在缓存引擎中使用初始路由

MyApplication.java
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);
  }
}
MyApplication.kt
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)
  }
}

通过设置导航通道的初始路由,关联的 FlutterEnginerunApp() Dart 函数的初始执行时显示所需的路由。

runApp() 的初始执行后更改导航通道的初始路由属性无效。希望在不同的 ActivityFragment 之间使用相同的 FlutterEngine 并切换这些显示之间的路由的开发者需要设置一个方法通道,并明确指示其 Dart 代码更改 Navigator 路由。

添加一个半透明的 Flutter 屏幕

Add Flutter Screen With Translucency Header

大多数全屏 Flutter 体验都是不透明的。但是,一些应用希望部署看起来像模态的 Flutter 屏幕,例如对话框或底部工作表。Flutter 支持开箱即用的半透明 FlutterActivity

要使 FlutterActivity 半透明,请对创建和启动 FlutterActivity 的常规流程进行以下更改。

步骤 1:使用具有半透明的主题

对于使用半透明背景渲染的 Activity,Android 需要一个特殊的主题属性。使用以下属性创建或更新 Android 主题

<style name="MyTheme" parent="@style/MyParentTheme">
  <item name="android:windowIsTranslucent">true</item>
</style>

然后,将半透明主题应用于 FlutterActivity

<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/MyTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  />

您的 FlutterActivity 现在支持半透明。接下来,您需要使用显式透明度支持启动 FlutterActivity

步骤 2:使用透明度启动 FlutterActivity

要启动具有透明背景的 FlutterActivity,请将适当的 BackgroundMode 传递给 IntentBuilder

ExistingActivity.java
// Using a new FlutterEngine.
startActivity(
  FlutterActivity
    .withNewEngine()
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(context)
);

// Using a cached FlutterEngine.
startActivity(
  FlutterActivity
    .withCachedEngine("my_engine_id")
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(context)
);
ExistingActivity.kt
// Using a new FlutterEngine.
startActivity(
  FlutterActivity
    .withNewEngine()
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(this)
);

// Using a cached FlutterEngine.
startActivity(
  FlutterActivity
    .withCachedEngine("my_engine_id")
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(this)
);

现在,您拥有一个具有透明背景的 FlutterActivity