7.2 Add-to-App:把 Flutter 嵌进现有原生应用


7.2 Add-to-App:把 Flutter 嵌进现有原生应用

本节摘要:上一节是 Flutter 应用向外要能力,本节是反向操作:把 Flutter 模块整块嵌进一个已有的 Android 或 iOS 工程。老应用不必推倒重写,新页面用 Flutter 逐块替换。本节讲清 FlutterEngine 的接入姿势、内存代价、跳转与状态设计,以及这条路真正的适用边界。

什么时候选嵌入:一条务实的分界

推倒重写是移动行业最贵的行为之一。一个跑了多年的原生应用,业务逻辑、埋点体系、线上验证全在存量代码里,重写意味着把所有踩过的坑重新踩一遍。Add-to-App 给出中间路线:老壳子继续跑,新需求用 Flutter 写,按页面或按功能块逐步替换。轻记账团队服务过一个电商客户的真实案例:主工程八年原生,商品详情页改版用 Flutter 实现,半年内按页面替换了十一屏,主工程全程可发版——这就是嵌入路线的节奏。

适合嵌入的信号:存量原生工程庞大且活跃(重写不现实);新需求界面密集、迭代快(Flutter 的主场);团队里 Flutter 与原生人手并存。不适合的信号:存量工程年久失修没人敢动(壳本身已是风险,先救壳);只有零星一两个页面(引入整套 Flutter 工具链的固定成本不划算,平台通道调单个能力即可)。

FlutterEngine:一块界面如何住进原生工程

嵌入的基本单元是 FlutterEngine——一个 Dart 运行时加渲染管线的完整实例。原生工程以模块形式引入 Flutter 代码后,三步接出界面。Android 侧(Kotlin):

// 一、预先启动引擎(应用启动的合适时机),并指定入口路由 val engine = FlutterEngine(context) engine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ) // 二、需要展示时把引擎装进容器(全屏页或 Fragment 均可) val flutterFragment = FlutterFragment.withEngine(engine) .initialRoute("/stats") // 直接落在统计页:路由表见第 4 章 .build() supportFragmentManager .beginTransaction() .replace(R.id.flutter_container, flutterFragment) .commit() // 三、应用退出或模块下线时释放引擎 engine.destroy()

iOS 侧同构:引擎用 FlutterEngine(dartEntrypoint:) 预热,展示时交给 FlutterViewController。预热时机是体验关键——引擎冷启动带着虚拟机初始化与首帧渲染,用户点按钮才启动就是一次肉眼可见的白屏;在应用启动的闲时预热,跳转时已是热的。

引擎复用是内存的关键账。每个引擎是一整个运行时实例(几十 MB 起步),老应用里开一堆引擎等于内存灾难。标准做法是单例引擎加多路由:一个引擎常驻,跳转到不同 Flutter 页面只是推送不同路由,引擎不增不减。代价是所有 Flutter 页面共享一个导航栈——原生页面与 Flutter 页面交错的导航序列要专门设计(通常约定:Flutter 侧只见自己内部的路由栈,跨栈边界由原生壳管理)。

边界设计:跳转、状态与通信

嵌入工程的日常复杂度集中在原生与 Flutter 的边界上,三条约定让边界可控:

跳转边界单向化。 约定"原生壳负责跨栈跳转":Flutter 页面想跳原生页,通过平台通道(上一节的手艺)发一条消息给壳,壳执行原生导航;反向同理。避免两侧互相持有导航器、跳转路径蜘蛛网化。轻记账给客户的实现是一个专门的 NavigationChannel,方法名就三件:跳原生、跳 Flutter、回退。

状态边界共享化。 用户态(登录令牌、用户偏好)别在两侧各存一份——第 4 章的状态唯一来源纪律在混合工程里升级为"全局唯一来源":令牌由原生壳持有,Flutter 侧经通道按需读取;或反向托管给 Flutter 侧、原生侧读取。谁持有都行,两份必打架

工程边界模块化。 Flutter 模块的依赖清单、资源、路由表自成一个封闭单元,原生壳只依赖模块的入口方法(初始化、跳转、销毁)。模块边界清晰,未来"整体迁出"或"整块下线"才有操作的抓手。

代价清单与验证姿势

嵌入不是免费午餐,四笔账要提前算:内存(常驻引擎的固定开销,低端机的敏感指标,接入前后要做内存对照);包体积(引擎加产物进主包,iOS 侧可用动态下发框架缓解,但要过商店审核的关);启动体验(预热策略决定首屏白屏与否,预热又与内存互相牵制);调试链路(问题横跨两侧,日志体系要合并——原生崩溃与 Dart 异常接进同一个监控平台,否则线上排查要在两套工具间反复横跳)。

验证姿势同样要混合:第 6 章的测试防线照跑(Flutter 模块内部),再加一层边界集成用例——原生跳 Flutter、Flutter 跳原生、跨边界的登录态同步,这三个场景手测加自动化各来一遍。嵌入工程的线上事故,多半藏在边界上而不是两侧内部。

嵌入工程的三个高频坑

坑一:Flutter 模块的依赖冲突污染主工程。 Flutter 模块带来的 Android 依赖与主工程既有依赖版本不合,构建期爆冲突。处理顺序:先在 Flutter 模块侧对齐版本(主工程的版本是既成事实,别倒过来),确实无解再用排除规则。每次升级 Flutter 版本后重跑一遍集成构建——工具链版本变更常改变生成的依赖清单。

坑二:入口路由失效。 预热引擎后跳转,落点不是预期的页面。多数根因是 executeDartEntrypoint 与 initialRoute 的先后关系没理清:路由参数要在引擎执行入口之前就位,晚了的参数只会作用于下次。把"预热与跳转"封装成一个门面方法,调用方不接触引擎细节,这类时序问题就收敛在一处。

坑三:内存告警时引擎不自救。 系统内存告警会通知到原生壳,但引擎不会自动释放缓存。壳要在告警回调里显式通知 Flutter 侧清理图片缓存与非关键状态——这条通道值得在接入第一天就修好,等线上内存事故再补,排查成本远高于实现成本。

渐进迁移的节奏管理

嵌入路线跑得好的团队,都有一条显性的迁移节奏。推荐的三段式:试金石阶段挑一个低风险、高感知的页面(通常是设置页或活动页)完成全链路接入,目的是把构建集成、边界约定、监控合并这些一次性成本全部付清;替换阶段按页面批量推进,每批保持"原生页数下降、崩溃率不升"的双指标验收;收敛阶段决定停止线——核心的启动与支付流程留在原生还是迁走,取决于团队 Flutter 化的深度,而不是迁移的惯性。三段式里最容易被忽略的是停止线:没有预先声明的终点,渐进迁移会退化成永久双栈。

配套的管理动作只有一个:每次迭代记录双栈成本(两侧维护的页面数、边界通道数、混合事故数)。这组数字既是向管理层汇报迁移收益的依据,也是决定停止线的原料——让数据而不是立场来终结这场迁移。

本节要点回顾

  • 嵌入适合庞大活跃的存量工程按页面渐进替换,零星一两个页面不如平台通道划算;
  • 引擎冷启动有白屏成本,闲时预热;单例引擎加多路由是内存与体验的平衡点;
  • 边界三约定:跳转单向化(壳负责跨栈)、状态唯一来源(两侧不存两份)、工程模块化(模块自封闭);
  • 四笔提前账:内存、包体积、启动体验、合并的监控链路;
  • 边界集成用例是混合工程的专属防线,事故多藏在边界不在两侧内部。

点状能力与整块嵌入都通了。下一节把 Flutter 带去两块新大陆:Web 与桌面,看同一份代码在老家之外要改什么。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U