Flutter Android 项目升级指南:将 pre-1.12 旧版 Embedding 迁移到新版 Android Embedding
适用对象:凡是在 Flutter 1.12 之前使用
flutter create创建的 Android 项目,都可能需要参照本指南进行升级。本文以 Flutter 官方仓库文档 Upgrading-pre-1.12-Android-projects.md 为核心骨架,并结合本仓库(Flutter 主仓库)内真实的引擎源码与构建工具实现,系统讲解旧版io.flutter.appEmbedding 到新版io.flutter.embedding.androidEmbedding 的迁移步骤,涵盖全 Flutter 应用与 Add-to-app(混合工程)两类场景。读完本文,你将掌握 MainActivity / Manifest / 启动屏 / 插件注册的完整迁移方法,并理解flutterEmbedding清单标记、${applicationName}占位符与插件自动注册背后的底层原理。
本指南面对的是历史上以 flutter create 创建、宿主代码未经手工改动的标准项目,以及曾按实验性 Add-to-app 流程接入 Flutter 的混合工程。如果你属于后一种情况,请直接阅读文末的 Add-to-app 迁移章节。
背景:为什么旧版 Android 宿主包装类被废弃
为了更好支持「向既有 Android 工程中嵌入 Flutter」这类真实场景,旧版在 engine/src/flutter/shell/platform/android/io/flutter/app 中承载 Flutter 运行时的宿主包装类(以 io.flutter.app.FlutterActivity 为代表)已被废弃,取而代之的是位于 engine/src/flutter/shell/platform/android/io/flutter/embedding/android 下的新版包装类(以 io.flutter.embedding.android.FlutterActivity 为代表)。
两者的定位差异可以从当前仓库的源码分布得到印证:
- 在本仓库的引擎 Android 侧源码中,
io.flutter.app包仅保留了为兼容旧项目而存在的占位类 FlutterApplication.java,且被@Deprecated标注,其类注释明确写着:Flutter 项目如需自定义Application,应改继承android.app.Application; - 旧实验性 Embedding 所在的
io.flutter.facade目录在仓库中已为空,其职责完全移交给了 embedding/android 包下的新类族; - 新版包装类提供了大量面向真实世界场景的设计,例如允许
FlutterActivity不再是应用中第一个、也是唯一一个 Activity,支持在 Fragment 中展示 Flutter 等。
动机:迁移与否的影响范围
- 已有全 Flutter 项目(full-Flutter)并不会立即受到影响,在可预见的未来仍能正常构建运行;
- 但新 Android 包装类同时引入了一套全新的 Android 插件开发 API。只在新插件 API 上开发的插件,将无法在老式(pre-1.12)Android 项目中工作——除非插件开发者主动额外实现一份向后兼容代码,否则在 pre-1.12 项目里使用 1.12 之后创建的插件,构建时会直接报错;
- Add-to-app 在旧 Android API 上并非官方支持。若你是在 1.12 之前依据实验性 wiki 教程接入 Flutter 的,请参考本文的 Add-to-app 迁移 章节。
新版 Embedding 插件模型的核心区别在于:旧插件通过 PluginRegistry.Registrar 注册,新插件则直接对着一个 FlutterEngine 注册(实现 FlutterPlugin 并响应 onAttachedToEngine)。
新旧两代 Embedding 的类对照
| 职责 | 旧版(pre-1.12,已废弃) | 新版(推荐) |
|---|---|---|
| Activity 宿主 | io.flutter.app.FlutterActivity |
io.flutter.embedding.android.FlutterActivity |
| Fragment 宿主 | io.flutter.facade.FlutterFragment(实验性) |
io.flutter.embedding.android.FlutterFragment |
| 视图宿主 | io.flutter.facade.Flutter.createView(...) / io.flutter.view.FlutterView |
io.flutter.embedding.android.FlutterView |
| Application | io.flutter.app.FlutterApplication |
直接继承 android.app.Application |
| 插件注册 | GeneratedPluginRegistrant.registerWith(Activity) |
GeneratedPluginRegistrant.registerWith(FlutterEngine)(通常自动完成) |
关于「插件注册自动完成」这一点,引擎源码给出了明确证据:引擎包下的 GeneratedPluginRegister.java 会在 FlutterEngine 构造期间通过反射查找 io.flutter.plugins.GeneratedPluginRegistrant 并调用其 registerWith(FlutterEngine)。也就是说,只要清单中正确声明了 flutterEmbedding 版本(见下文步骤 5),构建期由 Flutter 工具生成的 GeneratedPluginRegistrant 就会使用面向 FlutterEngine 的新式注册方式,实现插件自动装载。若想手动干预注册时机,可以:
- 自行构造
FlutterEngine时,将automaticallyRegisterPlugins构造参数置为false; - 或让
FlutterActivity/FlutterFragmentActivity隐式创建引擎,并覆写configureFlutterEngine但不调用父类实现。
Full-Flutter 应用迁移
本节假设你没有为 Flutter 工程手工改动过 Android 宿主工程;如果改过,请直接跳到 Add-to-app 迁移。
以下步骤以 1.12 之前的标准 flutter create 工程为操作对象。
步骤 1a:清空自带代码的 MainActivity
如果 android/app/src/main/java/[your/package/name]/MainActivity.java 中没有任何你自行添加的代码,那么只需删掉方法体并替换 FlutterActivity 的 import 即可。新版 FlutterActivity 不再要求你手动注册插件——当底层 FlutterEngine 创建时,它会在内部自动完成注册(底层逻辑见上文 GeneratedPluginRegister.java)。
// MainActivity.java
-import android.os.Bundle;
-import io.flutter.app.FlutterActivity;
+import io.flutter.embedding.android.FlutterActivity;
-import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- GeneratedPluginRegistrant.registerWith(this);
- }
}
Kotlin 版本同理:
// MainActivity.kt
-import android.os.Bundle
-import io.flutter.app.FlutterActivity
+import io.flutter.embedding.android.FlutterActivity
-import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- GeneratedPluginRegistrant.registerWith(this)
- }
}
由于 MainActivity 的方法体已经为空,你还可以直接删除 MainActivity.java/kt 文件。如果选择删除,就需要把 AndroidManifest.xml 中对 .MainActivity 的引用改为完整类名 io.flutter.embedding.android.FlutterActivity。
步骤 1b:迁移已有自定义 Platform Channel 代码
如果 MainActivity.java 中原本就有自定义 Platform Channel 的处理逻辑,可按下面示例切换到新版 Embedding API:
-import io.flutter.app.FlutterActivity;
-import io.flutter.plugin.common.MethodCall;
+import androidx.annotation.NonNull;
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
-import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
-import io.flutter.plugin.common.MethodChannel.Result;
+import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "samples.flutter.dev/battery";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
-
- super.onCreate(savedInstanceState);
- GeneratedPluginRegistrant.registerWith(this);
-
- new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
- new MethodCallHandler() {
- @Override
- public void onMethodCall(MethodCall call, Result result) {
- // Your existing code
- }
- });
- }
+
+ @Override
+ public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
+ GeneratedPluginRegistrant.registerWith(flutterEngine);
+ new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
+ .setMethodCallHandler(
+ (call, result) -> {
+ // Your existing code
+ }
+ );
+ }
}
迁移要点:
- 把
onCreate中注册 Channel 的代码整体搬进configureFlutterEngine覆写方法。从源码看,FlutterActivity.java 及其委托实现 FlutterActivityAndFragmentDelegate.java 会在引擎创建时回调该覆写点,让子类有机会对FlutterEngine做最后定制; - Binary Messenger 不再取自
getFlutterView(),而是改用flutterEngine.getDartExecutor().getBinaryMessenger(),这与新版MethodChannel面向FlutterEngine(而非视图)的架构完全对应; - 若你的工程是纯 Flutter 应用且依赖自动插件注册,则无需再显式调用
GeneratedPluginRegistrant.registerWith(flutterEngine)。
步骤 2~3:打开 Manifest,把 FlutterApplication 替换为 ${applicationName}
打开 android/app/src/main/AndroidManifest.xml。
替换前:
<application
android:name="io.flutter.app.FlutterApplication"
>
<!-- code omitted -->
</application>
替换后:
<application
android:name="${applicationName}"
>
<!-- code omitted -->
</application>
说明:${applicationName} 是构建期由 Flutter/Gradle 工具注入的占位符,它会在构建时被解析为实际的 Application 类名。这一点可以从本仓库的工程模板得到印证:AndroidManifest.xml.tmpl 中现代 flutter create 生成的 Manifest 正是使用 android:name="${applicationName}" 而非任何硬编码的 Flutter Application 类。
步骤 4:更新启动屏(Splash)行为
如果需要保留启动屏行为:
- 删除所有
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"的<meta-data>标签; - 在
styles.xml中新增一个「启动主题」,把期望的启动画面作为背景Drawable配置:
<!-- You can name this style whatever you'd like -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/[your_launch_drawable_here]</item>
</style>
如果你的工程是用 flutter create 创建的,通常已经带有一个 LaunchTheme 和一个名为 launch_background 的 drawable,可以直接复用并按需调整。
- 再新增一个「正常主题」,用于 Android 进程完全初始化后替换启动画面:
<!-- You can name this style whatever you'd like -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/[your_normal_background_drawable]</item>
</style>
「正常主题」绘制在 Flutter 内容背后的背景之上,通常只在第一帧 Flutter 画面渲染前短暂可见;同时它也在 Flutter 体验存续期间控制 Android 状态栏与导航栏的视觉属性。
- 配置
MainActivity先以启动主题开始、随后切到正常主题,并指定启动画面持续显示到 Flutter 渲染出第一帧为止:
<activity android:name=".MainActivity"
android:theme="@style/LaunchTheme"
// some code omitted
>
<!-- Specify that the launch screen should continue being displayed -->
<!-- until Flutter renders its first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<!-- Theme to apply as soon as Flutter begins rendering frames -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- some code omitted -->
</activity>
注意上述 io.flutter.embedding.android.SplashScreenDrawable 与 io.flutter.embedding.android.NormalTheme 两个 <meta-data> 键名均为新版 Embedding 的命名空间,在 现代工程模板 中也能看到 NormalTheme 的同样写法。
步骤 5:声明新版 Embedding 版本号
在 <application> 标签下新增一个 <meta-data> 标签:
<meta-data
android:name="flutterEmbedding"
android:value="2" />
一旦在 AndroidManifest 中做出该声明,并且工程使用了插件,那么 Flutter 工具在构建期间生成的新版 GeneratedPluginRegistrant 就会采用新版 Android Embedding 的插件注册方式——即把插件注册到一个 FlutterEngine 上,而不是注册到 PluginRegistry.Registrar 上。
flutterEmbedding 这一标记的重要性也可以从构建工具源码得到印证。在 project.dart 的 computeEmbeddingVersion() 实现中,Flutter 工具会解析应用的 AndroidManifest.xml 并据此判定工程所使用的 Embedding 版本:
- 若
<application>的android:name等于io.flutter.app.FlutterApplication,判定为 v1; - 若
<meta-data android:name="flutterEmbedding" android:value="1">,判定为 v1; - 若
<meta-data android:name="flutterEmbedding" android:value="2">,判定为 v2; - 若既没有 v2 标记、Manifest 文件也不存在等异常情况,同样按 v1(或错误)处理;Add-to-app 模块与插件工程则只支持 v2。
完成上述步骤后,你的应用仍可照常构建(例如执行 flutter build apk),但此时底层使用的已经是新版 Android 类了。
Add-to-app 迁移
本节针对用 Flutter 实验性 Embedding 实现的 Add-to-app 场景,说明如何将代码迁移到稳定版 Embedding。
与全 Flutter 应用相同的步骤
上文「Full-Flutter 应用迁移」中的部分步骤同样适用,请按顺序完成:
- 步骤 3:从
<application>标签移除对FlutterApplication的引用,替换为${applicationName}; - 步骤 4:更新启动屏行为(若需要保留启动屏);
- 步骤 5:在
<application>下新增flutterEmbedding = 2的<meta-data>标记。
Add-to-app 特有改动
如果你的代码中调用了 FlutterMain.startInitialization(...) 或 FlutterMain.ensureInitializationComplete(...),请删除这些调用。新版 Flutter 会在合适的时机自行初始化。
迁移 FlutterActivity 的用法
Add-to-app 场景往往涉及对 FlutterActivity 子类的修改——例如新增 MethodChannel、使用自定义 FlutterEngine 实例、自定义启动屏行为等,这些都需要覆写父类方法。因此,与全 Flutter 应用可以直接删掉 MainActivity 换成标准 FlutterActivity 不同,Add-to-app 场景通常需要保留子类以维持你的行为覆写。
如果你在 FlutterActivity 内部没有改动任何行为,应删除自己的子类并改用标准 FlutterActivity(方法见上节)。反之,若确需在 FlutterActivity 内修改行为,则要把代码从旧的 io.flutter.app.FlutterActivity 迁到新的 io.flutter.embedding.android.FlutterActivity:
迁移前:
package [your.package.name];
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
// ...some amount of custom code for your app is here.
}
迁移后:
package [your.package.name];
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
// You do not need to override onCreate() in order to invoke
// GeneratedPluginRegistrant. Flutter now does that on your behalf.
// ...retain whatever custom code you had from before (if any).
}
部分应用之前可能需要「预热」(pre-warm)Flutter 体验。现在官方建议所有 Add-to-app 场景都预热 Flutter 体验,以获得首帧渲染时的最佳视觉效果。预热对应的是「缓存 FlutterEngine」方案:从源码看,FlutterActivity.java 提供 withCachedEngine(String cachedEngineId) 构建器(配套 CachedEngineIntentBuilder),可让 Activity 复用一个预先创建并缓存的 FlutterEngine,从而跳过引擎初始化延迟。
完成上述修改后,你的 FlutterActivity 子类就已升级到新版稳定版 Android Embedding。
迁移 FlutterFragment 的用法
实验性 Embedding 提供的是 io.flutter.facade.FlutterFragment,以及 io.flutter.facade 包中的其他类。整个 io.flutter.facade 包均已废弃,不应再使用其中任何类。
实验性的 io.flutter.facade.FlutterFragment 已被 io.flutter.embedding.android.FlutterFragment 取代,后者面向远比原版更广泛的使用场景设计。如果你是通过 Flutter.createFragment(...) 实例化 io.flutter.facade.FlutterFragment,请删除这些调用,改用新类的以下工厂方法之一:
FlutterFragment.createDefault()FlutterFragment.withNewEngine()FlutterFragment.withCachedEngine(...)
从 FlutterFragment.java 的源码结构看,withNewEngine() 返回 NewEngineFragmentBuilder,可进一步链式指定 dartEntrypoint、dartLibraryUri、dartEntrypointArgs、initialRoute、appBundlePath、flutterShellArgs、renderMode、transparencyMode 等参数;withCachedEngine(String engineId) 则返回 CachedEngineFragmentBuilder,支持通过 destroyEngineWithFragment(...) 控制 Fragment 销毁时是否一并销毁引擎,并同样可设置 renderMode / transparencyMode。这些能力正是旧版 facade.FlutterFragment 所不具备的。
迁移 FlutterView 的用法
已废弃的 io.flutter.facade.Flutter 类含有一个名为 createView(...) 的工厂方法,该方法连同 io.flutter.facade 包中的其他代码一起被废弃。
Flutter 目前并没有为「直接在 View 层级使用 Flutter」提供便捷 API,因此应尽量避免 FlutterView 的使用。不过,技术上仍可以展示一个 FlutterView(确有需要时):务必使用 io.flutter.embedding.android.FlutterView,而不是 io.flutter.view.FlutterView。新的 FlutterView 可以像其他 Android View 一样直接实例化,随后按该类的 Javadoc 指引展示 Flutter 画面。
迁移验证与常见问题
迁移完成后,建议按以下顺序验证:
- 确认
AndroidManifest.xml已满足三处关键改动:${applicationName}替换了io.flutter.app.FlutterApplication;旧启动屏<meta-data>已替换为io.flutter.embedding.android.SplashScreenDrawable/io.flutter.embedding.android.NormalTheme;<application>下存在flutterEmbedding = 2; - 重新构建:
flutter build apk(或运行flutter run),观察是否仍能正常编译、安装与渲染; - 若工程内使用了 1.12 之后开发的插件,请确认插件基于新插件 API 开发(依赖自动注册),否则旧工程会出现构建期插件错误——这正是本指南强调第 5 步
flutterEmbedding标记的原因所在。
如果在迁移后仍遇到与旧 API(io.flutter.app.*、io.flutter.facade.*、FlutterApplication)相关的引用错误,可回到 背景 一节的类对照表,逐一核对 import 与 Manifest 引用是否已指向新版命名空间。
此外需要区分:本文针对的是 1.12 之前创建的历史工程。若是用现代版本 flutter create 创建的项目,其模板(如 AndroidManifest.xml.tmpl)本身就带 flutterEmbedding = 2、${applicationName} 与 NormalTheme 等配置,直接基于 v2 Embedding,不受本迁移影响。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0626
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00