一、背景

在终端App开发的过程中往往面临着Android、iOS、Mac、PC、web 5端,现在还有鸿蒙端,大型工程千万行级别的代码量让每一次需求迭代都需要多端同步开发、发版,这对研发、产品、设计、测试全流程都是巨大挑战。不少超大型工程在初期架构设计中,会将底层网络、数据库及大部分业务逻辑抽离至 C++ 层实现多平台复用,但 UI 层仍由各平台独立开发,移动端与电脑端即便实现相同界面布局,也需要编写多台套逻辑代码,UI 跨平台成为行业普遍的核心痛点。
请添加图片描述
业内曾尝试过H5、小程序等UI跨平台方案,但受限于性能或者体验,无法覆盖大部分核心业务场景,因此开发者一直在寻找高性能的跨平台UI方案。直到Flutter推出后,经过Demo验证发现,其体验效果媲美原生,底层基因Skia(现在是Impeller)自绘引擎渲染,能满足高复杂度业务需求,同时丰富的社区生态也加速了框架的成熟落地,成为超大型工程跨平台 UI 开发的优选方案。

一、Flutter 工程架构设计

在这里插入图片描述
Flutter 提供了 Application(独立 App)、Module(add2app)、Plugin(包含 Android/iOS/Dart 代码)、Package(纯 Dart 代码)四种工程模块形式。对于已有成熟项目的超大型工程,更适合采用Flutter Module的形式接入,同时借鉴原生开发的组件化经验,让各业务模块相互独立,提升项目管理效率。
在模块交互与实现层面,不同业务模块不可避免需要与原生进行交互,而直接使用 Plugin 存在明显弊端:一是每个 Plugin 会被打成独立 AAR 包,源码依赖模式会增加项目维护难度;二是 iOS 端每个 Plugin 会生成独立 Framework,数量过多会影响应用启动速度。
因此,超大型工程的 Flutter 架构设计可遵循以下原则:

  1. 核心业务代码通过Package 纯 Dart 模块实现,降低与原生的耦合;
  2. 与原生的交互通过 Channel 生成双端代码,由各端原生模块自行维护;
  3. 第三方 SDK 或插件按需通过 Plugin 引入,减少不必要的 AAR/Framework 生成;
  4. 底层下沉通用能力,封装独立的 UI 控件库、基础工具库和路由组件库,实现能力复用。

Package 组件的接口暴露可参考 Flutter 官方 API 设计思路,在 lib 目录下定义统一的对外 Dart 文件,内部粒度实现通过export关键字导入,保证接口的简洁性和可维护性:

library lib_flutter;

export 'src/cupertino/dialog.dart';
export 'src/cupertino/nav_bar.dart';
export 'src/cupertino/route.dart';

同时,可通过 Channel Pigeon 实现线上 Crash 监控等基础能力,打造分层清晰、高内聚低耦合的 Flutter 组件化架构。
请添加图片描述

二、混合栈开发方案设计

混合栈开发是指 App 中同时存在原生页面与 Flutter 页面,且支持页面间互相跳转,这也是目前大部分已有项目接入 Flutter 的主流形式。业内主流的混合栈方案有 FlutterBoost、FlutterThrio(腾讯自研) 等,各方案在导航效率、实现成本、性能开销、跨端支持等方面各有优劣,具体对比如下:

特性 FlutterBoost FlutterThrio(基于 Flutter 内部导航栈)
导航效率
实现 / 维护成本
性能开销
应用场景 全场景 初期局部场景,后期可扩展至全场景
接入成本
成熟度
MAC/PC 扩展支持

FlutterBoost 通过入侵原生 Flutter Navigator 栈,将栈管理统一由原生或 Flutter 内部接管,栈管理逻辑清晰但开发维护成本高;FlutterThrio 则直接使用 Flutter 原生导航栈,开发成本低且易扩展至桌面端,但官方文档相对匮乏。
针对超大型工程的需求,学习腾讯自研基于 Flutter 内部导航栈的混合栈方案是更优选择,核心设计思路为:

  1. 遵循 Flutter 官方路由设计,定义AppContainer作为基础容器,在 Engine 初始化阶段完成预热,同时完成基础配置、主题设置等全局操作;
  2. 具体页面打开时,通过 Channel 推送 MaterialApp 的 OverlayEntry 实现路由栈管理,Flutter 内部页面跳转完全由自身 Navigator 控制,无入侵性;
  3. 收拢基础导航逻辑,让业务开发仅需关注核心业务实现,同时提供全局导航栈插桩能力,方便做埋点、拦截等通用处理。

2.1 引擎使用与管理优化

Flutter Engine 开销较大,无法为每个 Flutter 页面绑定独立引擎,因此引擎的使用管理是混合栈开发的核心问题,可分为两个阶段逐步优化:
单引擎阶段
请添加图片描述
Flutter 2.0 版本前,采用单引擎缓存模式:Engine 初始化后进行全局缓存,所有 Flutter 页面均绑定该引擎,大幅降低引擎开销。但该模式存在局限性,无法支撑 “原生页面→Flutter 页面→Flutter 页面” 的多 Flutter 页面共存场景,仅能限制 Flutter 页面间不进行容器跳转,仅适用于初期局部业务接入 Flutter 的场景。

多引擎阶段
请添加图片描述
Flutter 2.0 版本推出FlutterEngineGroup,原生支持引擎内存空间复用,彻底解决了多引擎的内存开销问题,新增一个引擎的内存开销可控制在 4MB 左右,为多引擎模式落地提供了基础。
请添加图片描述

为兼顾性能与体验,超大型工程可采用主引擎 + 临时引擎的多引擎复用模式,而非简单的引擎与页面 1 对 1 绑定:

  • 当打开 Flutter 页面时,若栈中无其他 Flutter 页面,直接使用主引擎,最大化复用资源;
  • 当打开 Flutter 页面时,若栈中存在其他 Flutter 页面,使用临时引擎,并为页面自定义唯一引擎名称,临时引擎初始化后进行缓存,该页面再次打开时直接复用,优化启动速度;
  • 原生侧通过引擎预加载减少首次启动耗时,通过引擎复用降低整体性能消耗;
  • Flutter 侧以 AppContainer 为统一页面载体,实际页面打开时通过容器切换不同子页面,统一 Flutter 页面入口,减少原生与 Flutter 的交互成本。

三、Flutter 与原生通信体系建设

请添加图片描述

3.1. Pigeon 原生问题与优化需求

Pigeon 虽解决了基础的通信效率问题,但在超大型工程的复杂业务场景中,仍存在明显短板,最核心的问题是原生对 List、Map 等复杂数据类型支持较弱。在这里插入图片描述
核心原因在于 Pigeon 的传输逻辑:将 Dart Class 转换为 Map 后传输至原生,原生再将 Map 反解为 Class,对于基础类型无明显问题,但面对 List/Map,因缺乏通用的反序列化工具,toMap/fromMap 的代码复杂度会急剧上升;若遇到 List 与 Map 相互嵌套的场景,框架生成的代码会极度臃肿,且存在严重的性能损耗。请添加图片描述
而超大型工程中 List/Map 是高频使用的数据结构,同时多数工程已大规模使用 Protobuf(PB),因此对 Pigeon 的核心优化方向为:

  • 提升传输性能,避免嵌套数据带来的复杂计算;
  • 完善数据类型支持,兼容 List/Map 等复杂结构;
  • 复用已有 PB 结构,降低跨端数据转换成本。

3.2. Pigeon 传输数据结构优化

基于 Protobuf 的广泛使用,可对 Pigeon 源码进行改造,将原有的 “Dart Class→Map” 转换逻辑,优化为**“Dart Class→Protobuf”**,具体流程为:
请添加图片描述

该优化方案借助 Protobuf 对 List/Map 的原生支持,解决了复杂数据类型的传输问题,同时序列化 / 反序列化工具成熟,对现有工具链几乎零成本,大幅提升了通信的稳定性和效率。

3.3. Pigeon Channel Server 自动化注册

Pigeon 生成的 Server 需要在原生页面(如 Android Activity)中注册后,Flutter 页面才能通过 Channel 调用原生实现,而超大型工程中业务迭代频繁,易出现Flutter 页面跳转后 Channel Server 未注册的问题:
请添加图片描述
请添加图片描述

问题的难点,在于Anroid的channel server实现类,分散在不同的module中,跨module手动注册其他flutter页面的channel server实现类,繁琐且不够优雅,而且不同的flutter页面,往往是由不同的开发同事完成,互相的调用往往并不清楚哪些需要注册channel server,一旦遗漏,就会产生异常,且这种异常,由于业务路径的特殊性,开发和测试都难以检测出来,风险性更大。因此,设计了channel server的自动化注册流程:整体流程原理如下:
请添加图片描述

针对该问题,打造 Channel Server 自动化注册流程,避免跨模块手动注册的繁琐与遗漏,具体实现原理:

  1. 通过 Pigeon 自动生成各端通信胶水代码;
  2. Flutter 调用业务 Channel 时,先检测对应的 Server 是否已注册,若未注册,自动调用专属注册 Channel,通知原生端进行注册;
  3. 原生端收到注册请求后,从 Manifest 中读取 Channel Server 的全路径名(编译期自动生成),通过反射将实现类注册至当前页面,并通知 Flutter 注册成功;
  4. Flutter 收到注册成功消息后,重新发起业务 Channel 调用,完成通信。

四、Dart 与 C++ 跨语言调用演进

多数超大型工程的客户端底层已基于 Chrome Service 打造了成熟的跨平台业务架构,网络请求、数据处理等复杂逻辑均由 C++ 层实现,Android、iOS 仅负责 UI 绘制与联调。接入 Flutter 后,核心需求是复用底层 C++ 跨平台逻辑,避免重复开发,因此需要解决 Dart 与 C++ 的相互调用问题。

1. 初期实现方案:修改 Flutter Engine
DartVM 提供了Dart_SetNativeResolver方法,可加载 Dart 中标记为 native 的方法,Flutter Engine 内部也通过该方式实现与 Dart 的通信。初期可通过修改 Flutter Engine 源码的方式,将 Engine 内部的RegisterNatives和Dart_SetNativeResolver方法暴露出来,在合适时机装载自定义 C++ 模块。
该方案虽能实现 Dart 与 C++ 的调用,但存在明显弊端:需要长期维护自定义 Engine,与官方 Engine 版本脱节,后续升级和维护成本极高,不适用于超大型工程的长期发展。

2. 基础方案:Dart::FFI
Dart 2.5 版本后推出Dart::FFI,原生支持调用 C++ 接口,且可在 Flutter 中直接使用,成为替代修改 Engine 的主流方案。但 Dart::FFI 在超大型工程的实践中,仍存在诸多限制:

  • 调用步骤繁琐,接口维护和参数约束难度大,需要手动绑定方法并严格对齐参数类型;
  • C++ 回调 Dart 仅支持静态 / 顶级函数,灵活度低;
  • Dart 开放了指针的分配与释放,跨语言内存管理混乱,极易造成内存泄漏;
  • 接口绑定不匹配或 SO 文件未更新时,会触发全局异常,影响开发流程。

其中,异步接口的调用逻辑更为复杂:需要在 Dart Isolate 中启动监听端口,C++ 通过Dart_PostCObject将函数指针传递给 Dart,再通过 FFI 在 Flutter UI 线程执行,跨线程逻辑繁琐且易出问题。

3. 优化方案:基于 Dart::FFI 实现轻量 RPC 框架
针对 Dart::FFI 的痛点,超大型工程可参考 gRPC/TRPC 的设计思路,实现一套基于 Dart::FFI 的轻量 RPC 框架,核心目标是:减少手写胶水代码、实现内存可控管理、保证线程安全,让开发者无需关注指针操作和线程切换。

核心架构设计
在这里插入图片描述

  • 基于 Protobuf 定义 Dart 与 C++ 的调用接口,数据结构统一为 PB,实现前后端 / 跨语言模型复用;
  • C++ 层引入 RPC 的服务发现与服务调用能力,Dart 层生成对应的 Stub;
  • 去掉 RPC 的网络通信机制,基于 Dart::FFI 实现客户端(Dart)与服务端(C++)的本地通信;
  • 框架层统一管理内存分配与释放、线程切换,屏蔽底层细节。

具体调用流程
在这里插入图片描述

  • 在 Protobuf 文件中定义 RPC 方法和数据模型;
  • 通过脚本自动生成 Dart 侧 Client Service 和 C++ 侧 Service 接口;
  • C++ 层实现生成的接口,并将 Service 注册至全局的 LanguageCallServer;
  • Dart 侧通过 Protobuf 生成的 RpcServiceApi 直接调用 C++ 方法,调用方式与本地异步方法一致:
final GovernRpcServiceApi api = GovernRpcServiceApi(WeRpcClient());
final RpcResult<GetGovernMyReportListResp> result = await api.getGovernMyReportListFromServer(GetGovernMyReportListReq()..limit = 10);

该方案除 C++ 侧的业务逻辑实现外,其余胶水代码、接口绑定均由框架自动生成,大幅提升开发效率,同时实现了跨语言调用的标准化、规范化。

五、Flutter 性能全方位优化

超大型工程对性能要求严苛,Flutter 接入后需针对启动卡顿、图片加载、资源体积等核心问题进行专项优化,保障体验媲美原生。

5.1. 着色器卡顿优化

Flutter 在首次进入复杂页面时,易出现着色器编译卡顿和白屏问题,核心原因是 Skia 引擎的 GPURasterizer::Draw 操作耗时过长,部分场景耗时可达数百毫秒。且在 Flutter 2.3 版本前,iOS 端 Skia 的持久缓存存在失效问题,直到 2.3 Beta 版本后才原生支持 iOS Metal 渲染。
针对add2app 接入模式,可通过以下方式优化:

  1. 在 iOS 端通过launchArguments添加 Flutter 启动变量,开启着色器缓存:
    flutter run --profile --cache-sksl --purge-persistent-cache
    

在这里插入图片描述

  1. 生成着色器缓存文件io.flutter.shaders.json,将其放置在项目根目录,并添加至 Flutter 的 asset 资源中:
    flutter:
    assets:
    	- io.flutter.shaders.json
    
    通过预缓存着色器,避免首次进入页面时的实时编译,彻底解决着色器卡顿问题。

5.2. 跨端图片缓存框架自研

Flutter 原生无磁盘缓存能力,社区主流的cached_image_network虽能实现硬盘缓存,但在混合栈开发中存在明显问题:原生端已有成熟的图片缓存框架,Flutter 与原生加载同一张图片时会重复下载、重复存储,且社区框架无法适配原生复杂的下载策略(如断点续传、缓存过期策略等)。
因此,超大型工程需自研打通 Flutter 与原生的图片缓存框架,核心流程为:

  • Flutter 侧加载图片时,先检查自身内存缓存,若存在直接使用;
  • 无内存缓存时,通过 Channel 调用原生侧的图片缓存逻辑,优先读取原生硬盘缓存;
  • 原生硬盘缓存无该图片时,通过原生的网络下载通道加载图片,并完成双端缓存同步;
  • 图片加载完成后,返回至 Flutter 侧渲染,实现 Flutter 与原生的图片缓存复用。
    在这里插入图片描述

5.3. SVG 转 IconFont:资源体积与渲染性能优化

Flutter 目前无法直接使用原生图片资源,需单独维护一套图标库,而传统 PNG 图标存在包体积大、低端机型渲染闪屏等问题;SVG 虽为矢量图,但无官方支持,依赖第三方包后渲染性能较差。
IconFont(字体图标) 成为最优解,其具备矢量特性、颜色可灵活修改、渲染性能高、包体积小等优势,也是 Flutter 官方 MaterialIcons、CupertinoIcons 的实现方式。三种图标格式对比如下:

特性 PNG SVG IconFont
官方支持 不支持
应用场景 丰富 部分 纯色
渲染性能
包大小

自动化构建流程
基于工程化工具,打造 SVG 到 IconFont 的自动化转换流程,减少人工维护成本:

  • 在持续集成平台(如蓝盾)部署 Node.js 环境并安装 Gulp;
  • 配置监听脚本,实时监控项目中 SVG 资源的新增 / 修改;
  • 自动将 SVG 转换为 IconFont 所需的 ttf 字体文件、Dart 索引文件(IconFont.dart)和预览静态 HTML;
  • 业务侧直接通过 Dart 索引使用字体图标,支持自定义颜色、大小,仅保留多色图标为 PNG 格式。

该方案可大幅降低图片资源体积,同时提升图标渲染性能,解决低端机型闪屏问题。

六、Flutter 生态体系建设

超大型工程的 Flutter 规模化落地,离不开完善的生态体系支撑,需针对多语言、原生体验、暗黑模式等核心场景打造标准化解决方案,提升开发效率和体验一致性。

6.1. 全闭环多语言框架建设

Flutter 原生无多语言框架支持,社区主流的flutter_intl虽能实现文字资源集中管理和多语言切换,但存在无法增量提取待翻译资源、无自动化翻译脚本、需手动维护翻译内容等问题,不适用于超大型工程的多端多语言管理。
在这里插入图片描述

针对该问题,需基于flutter_intl进行扩展,打造全闭环的多语言框架,核心是通过脚本工具补足框架能力,实现 “文本提取 - 增量翻译 - 资源写入” 的自动化,具体建设内容:
(1)Hardcode 文本自动化提取工具
开发 IDE 插件(如 string_extractor),支持快捷键一键识别代码中的硬编码文本,自动提取至 Flutter 多语言核心文件.arb 中,并支持自定义索引 ID 前缀,避免开发者手动提取的繁琐和遗漏,提升开发效率。
在这里插入图片描述
string_extractor 文本提取工具
通常来说,开发者在文字资源编写的时候,为了节省开发时间,不中断开发时的思路,往往会先将文字资源hardcode编写到代码中。待功能开发完之后,再将hardcode的文字资源统一提取到统一资源管理类中。这样后期的提取工作费时费力,且容易遗漏。
框架提供了string_extractor自动化hardcode文本资源提取的IDE工具,只需要安装到IDE中,使用快捷键option+e即可自动识别页面中的hardcode文本,并提取到.arb文件中。如图为string_extractor插件界面,支持自定义索引id前缀:
请添加图片描述
(2)增量翻译脚本
根据翻译类型打造两套自动化脚本,实现翻译资源的增量管理,避免全量翻译的重复工作:

  1. 中译英脚本(rescan_flutter,基于 Java 实现):
    findNeedTranslateRes:比对.arb 文件中的中 / 英文资源,增量提取未翻译的中文文本,优先从翻译缓存中匹配已有译文,无匹配则写入 Excel 供人工翻译;
    merge2res:将人工翻译后的英文资源写入.arb 文件,并更新翻译缓存。
    请添加图片描述

  2. 中译繁脚本(conversion2_flutter,基于 Python 实现):基于开源翻译 API,将.arb 文件中的中文资源自动翻译为繁体,并直接写入文件,实现全自动化。
    请添加图片描述

6.2. 仿原生动画与 UI 组件库打造

跨平台开发的核心命题是体验媲美原生,而原生 Flutter 组件在页面切换、点击态、导航栏动画等方面与原生平台存在明显差异,易出现设计走查不通过、用户体验不佳等问题。
因此,超大型工程需自研一套仿原生的 UI 控件库,基于 Flutter 官方组件进行二次封装,核心优化点:

  • 还原各平台原生的页面切换动画,适配 Android/iOS/ 桌面端的交互差异;
  • 优化组件点击态效果,贴合各平台的设计规范,避免无点击反馈或跨平台效果混乱;
  • 实现原生级别的导航栏动画,支持渐变、隐藏、滑动跟随等复杂效果;
  • 封装通用业务组件(如弹窗、列表、输入框),统一跨端样式和交互,减少业务侧重复开发。

6.3. 暗黑模式标准化适配

随着各平台对暗黑模式的原生支持,超大型工程需实现 Flutter 端的暗黑模式适配,且要保证与原生端的主题一致性,核心适配思路围绕系统主题管理和动态颜色定义展开:
(1)基于 MaterialApp 的主题统一管理
Flutter 的 MaterialApp 提供theme(浅色模式)和darkTheme(暗黑模式)两个配置项,官方组件(AppBar、Button、文本等)会默认从系统主题中读取样式参数。因此,可通过修改官方主题默认配置的方式,简化组件适配成本:

  • 分别定义浅色 / 暗黑模式的主题数据,包含颜色、字体、间距、分割线等通用样式;
  • 通用组件直接使用官方组件,无需手动设置样式参数,由主题统一控制;
  • 仅对特殊业务组件进行自定义样式配置,遵循 “主题优先,自定义为辅” 的原则。

示例:分割线主题配置

// 浅色模式
dividerTheme: const DividerThemeData(
  color: WWKLightColor.color_7,
  space: 0.33,
  thickness: 0.33,
));
// 暗黑模式
dividerTheme: const DividerThemeData(
  color: WWKDarkColor.color_7,
  space: 0.33,
  thickness: 0.33,
),
// 业务侧直接使用,无需重复配置
const Divider();

(2)基于 CupertinoDynamicColor 的动态颜色定义
利用 Flutter 的CupertinoDynamicColor实现颜色的动态切换,将项目中所有自定义颜色封装为动态颜色,并通过 Dart Extension 将颜色属性挂载至 BuildContext,方便业务侧调用:

Color get color73 => CupertinoDynamicColor.resolve(
  const CupertinoDynamicColor.withBrightness(
    color: Color(0xff32c757), // 浅色模式
    darkColor: Color(0xff38c95c), // 暗黑模式
  ), 
  _context
);
// 业务侧调用
context.color73

通过该方式,可实现颜色的全局统一管理,主题切换时自动更新所有组件颜色,无需业务侧单独处理。

七、Flutter 专属调试工具开发

超大型工程的 Flutter 规模化开发,需要一款适配自身业务特点的调试工具,覆盖效率调试、性能监控、问题排查全流程,替代社区通用工具的功能短板,提升研发、测试、设计的协同效率。以下为通用的 Flutter 调试工具(UiInsight-Flutter)设计思路,核心分为效率工具、性能工具、扩展工具三大模块,功能对比社区工具的优势如下:

功能 自研 UiInsight-Flutter 社区工具 DoKit Flutter 社区工具 UME
Widget 信息查看 ✔️ ✔️ ✔️
MethodChannel 调用监控 ✔️ ✔️
内存信息及泄露检查 ✔️ ✔️ ✔️
颜色拾取 ✔️ ✔️ ✔️
页面启动耗时统计 ✔️ ✔️
控件位置测量 ✔️ ✔️
控件间距离测量 ✔️ ✔️
全方法执行耗时统计 ✔️
图片检查 / 大图告警 ✔️
支持业务侧扩展 ✔️ ✔️
  1. 工具入口设计
    采用悬浮窗入口,接入后在界面上实时展示 FPS 和 Dart 虚拟机堆内存大小,方便开发者实时掌握应用性能状态:
    单击悬浮窗:展示更多基础性能数据(如页面 Widget 数量、引擎版本);
    双击悬浮窗:弹出功能弹窗,可按需开启 / 关闭各调试工具。
    请添加图片描述
    请添加图片描述

  2. 效率工具:提升开发与设计协同效率
    核心目标是帮助开发者快速还原设计稿、定位 UI 问题,减少开发与设计的沟通成本,核心功能:
    当前页面信息:识别页面的 Scaffold 元素,展示对应的 Widget 名、文件名和代码行数,无业务侵入性;
    请添加图片描述
    控件信息拾取:选中任意 Widget,展示其类名、所在文件、行号、坐标位置等详细信息;
    请添加图片描述
    位置拾取:拖拽选中环,实时获取中心点的 X/Y 坐标,精准匹配设计稿位置;
    请添加图片描述

控件间距离测量:支持选中控件某条边作为基准,实时测量与其他控件边的距离,支持平行边精准测距;
请添加图片描述
请添加图片描述
颜色吸管:拖拽选中环拾取屏幕任意像素点的色值,保证与设计稿的颜色一致性;
请添加图片描述

图片检查:检测图片源数据宽高与控件宽高的比例,识别大图加载问题,避免性能损耗。
请添加图片描述

  1. 性能工具:全维度监控应用性能
    核心目标是帮助开发者发现并定位 Flutter 应用的性能瓶颈,核心功能:
    FPS 树状图展示:以可视化树状图展示 FPS 实时变化,直观发现卡顿节点;
    请添加图片描述

大图检测告警:为 Image 组件配置自定义 frameBuilder,加载大图时展示告警图标,快速定位大图问题;

Image.network(                         
"https://img95.699pic.com/photo/40070/2524.jpg_wh860.jpg",     
frameBuilder:  FlutterInsight.instance.checkImage,    
 width: 100,     
 height: 50,
 )

可在图片宽高远大于控件宽高的Image组件中看到大图警告的图标:
请添加图片描述
内存详情及泄露检查:监控页面 Scaffold 元素的生命周期,检测内存泄露并实时告警,展示泄露详情;
请添加图片描述
请添加图片描述

MethodChannel 调用监控:实时监控 Flutter 与原生的 Channel 通信,展示调用方法名、参数、耗时、结果;
请添加图片描述
请添加图片描述

页面层级及加载耗时:统计每个页面的加载耗时、Widget 数量和层级深度,定位页面启动卡顿问题;
请添加图片描述
全方法执行耗时排行:基于 AOP 实现全方法插桩,统计方法执行耗时并排行,定位性能瓶颈方法。
请添加图片描述

全方法插桩实现思路(基于 AspectD 改造)
flutter在编译时,首先由frontend_server将dart代码转换为中间文件app.dill,然后在debug打包下,转换为kernel_blob.bin,release打包下,转换为so或framwork。请添加图片描述
flutter的Aop就是对app.dill进行修改实现的。AspectD是闲鱼针对Flutter实现的AOP开源库,可实现对项目中的方法进行插桩。全方法的插桩是我们基于AspectD进行修改实现的:
方案一:在aop_impl.dart中,通过添加Execute注解对所有方法进行插桩。在对类似build这种覆写方法插桩时,拿不到该方法对应的library,将产生nonenull报错,如: https://github.com/XianyuTech/aspectd/issues/124。
方案二:在aop_impl.dart中,通过添加Call注解对所有方法进行插桩。这个方案可以得到工程中的所有方法被调用时的耗时,但由于没有调用点,故无法得到如xxWidget的build方法的耗时,也无法满足我们的需求。最终方案:
请添加图片描述

  1. 首先app.dill将读取为Component变量。

  2. 通过遍历该component中的library、class、procedure,可得到工程中写aop_map_help.dart文件,并保存其markStart和markEnd函数为procedure。以便后续添加markStart调用和markEnd调用时使用。

  3. 考虑到一个方法的开始和结束存在以下几种情况:
    带返回的函数,需要在这个函数主体的开始添加markStart调用,需要在这个函数的return语句前添加markEnd调用。

     build(BuildContext context) {  
    int aa = 0;  
    if(aa == 1)return Text("test");  
    return Container(); 
    //结束时
    }
    

    不带返回的函数,需要在这个函数主体的开始添加markStart调用,需要在这个函数主体的结束添加markEnd调用,在这个的return语句前添加markEnd调用。

    void test1() { 
    int add =0;  
    if(add == 0) return;
    }
    

    我们可以通过RecursiveVisitor 提供的api访问app.dill中library、class、blockreturnElement,并实现上述的插桩行为。

  4. 插桩后解开app.dill可以看到:

    method test()void {     
    aop::MethodTrace::markStart("test", "Test", "file:///Users/shuushigeru/Documents/flutter/aspect_demo/lib/my_test_now.dart");      
    core::int* add = 0;     
    if(add.{core::num::==}(0)) {       
    		aop::MethodTrace::markEnd("test", "Test", "file:///Users/shuushigeru/Documents/flutter/aspect_demo/lib/my_test_now.dart");        
    		return;  
    	}     
    	aop::MethodTrace::markEnd("test", "Test", "file:///Users/shuushigeru/Documents/flutter/aspect_demo/lib/my_test_now.dart");   
     }
     method build(fra::BuildContext* context) → fra::Widget* {  
     aop::MethodTrace::markStart("build", "Test", "file:///Users/shuushigeru/Documents/flutter/aspect_demo/lib/my_test_now.dart");      
     core::int* aa = 0;      
     if(aa.{core::num::==}(1)) {        
     		dynamic value_build_16;        
    		value_build_16 = new text::Text::("test",$creationLocationd_0dea112b090073317d4: #C4290);        
    		aop::MethodTrace::markEnd("build", "Test", "file:///Users/shuushigeru/Documents/flutter/aspect_demo/lib/my_test_now.dart");       
     		return value_build_16;      
    	}     
     	dynamic value_build_17;      
     	value_build_17 = new con2::Container::($creationLocationd_0dea112b090073317d4: #C4291);      
     	aop::MethodTrace::markEnd("build", "Test", "file:///Users/shuushigeru/Documents/flutter/aspect_demo/lib/my_test_now.dart");      
     	return value_build_17;    
     }
    

est的函数开始、return处或结束处均插入了对应统计代码。

  1. 考虑到一般来说,我们更关注未被async修饰函数的耗时,可以在3、4操作前通过读取 procedure.function.dartAsyncMarker.index 先判断当前function是否为async函数,具体判断方式参考官网AsyncMarker enum,若为async函数,则不执行插桩。

  2. 扩展工具:适配业务个性化需求
    自研调试工具需具备高扩展性,支持业务侧根据自身特点添加自定义功能,提供跳转、展示、开关三种扩展类型,示例代码:

    FlutterInsight.instance.addDialogItem(UiItemWidget(
    title: "原生调用监控",
    showMore: true,
    onMorePressed: (context) {
    		// 跳转至原生调用监控页面
    	},
    ));
    FlutterInsight.instance.addDialogItem(UiItemWidget(
    	title: "打开测试模式",
    	checked: true,
    	onCheckBoxPressed: (isChecked){
    		// 测试模式开关逻辑
    	},
    ));
    

八、Flutter 动态化探索与实践

Flutter 虽具备优异的跨端渲染性能和一致性,但原生不支持动态化,而动态化能力对超大型工程至关重要:可实现更短的上线路径、更快的线上问题修复,同时优化应用安装包体积。因此,业内一直在探索基于 Flutter 的动态化方案,核心分为两大类。

  1. 业内主流 Flutter 动态化方案分类
    根据 DSL(领域特定语言)的不同,基于 Flutter 的动态化方案可分为面向前端和面向终端两类,各有优劣:
    面向前端的方案:基于 JS/TS 开发,兼容 W3C 规范,对前端开发者友好,代表框架有 LiteApp(微信自研)、Kraken(阿里开源);
    面向终端的方案:基于 Dart 开发,使用终端原生 IDE,对终端开发者友好,学习成本低,代表框架有 MTFlutter(美团出品,未开源)。
    其中,面向前端的方案是目前业内落地的主流,LiteApp 和 Kraken 的核心对比如下:
    开发支持:均兼容 W3C 规范的 DOM API,LiteApp 支持 Vue.js,Kraken 支持 HTML/CSS/React/Vue;
    跨端通信:Kraken 改造 dart:ffi 支持 Dart 与 C 双向调用;LiteApp 改造 Flutter Engine,增加 dart2cpp 模块,实现 C++ 与 Dart 的高效互通;
    渲染效率:Kraken 直接基于 Render Object 渲染,渲染管线更短;LiteApp 将虚拟 DOM 树映射为 Flutter Widget 树,渲染效率稍低,但兼容性更好;
    W3C 兼容:LiteApp 对 CSS 的支持更全面,支持富文本、Store 等,可满足线上业务需求;Kraken 的 CSS 支持较弱,暂适用于简单场景。
    下图从开发语言/框架、通信效率、渲染效率、等四个角度,对 LiteApp 和 Kraken 进行了调研和对比:
    请添加图片描述
  2. 通用 Flutter 动态化方案落地:基于 LiteApp
    结合超大型工程的业务特点,LiteApp是更易落地的 Flutter 动态化方案,核心架构分为宿主工程和上层动态化层,执行流程分为三个阶段:
    前端开发与解析:前端开发者使用 Vue.js 开发业务,生成的 zip 包可通过服务端下发至终端;终端通过封装后的 JS 引擎(JavaScriptCore/V8)解析运行,在原生 JS 基础库支撑下生成虚拟 DOM 树;
    虚拟 DOM 树映射:在 LuggageView 层将虚拟 DOM 树映射为 LuggageView 树,同时完成 CSS 属性解析和页面布局,再通过 dart2cpp 模块将 LuggageView 树传输至 Flutter 层;
    Flutter 渲染:Flutter 层将 LuggageView 树解析为 Element Tree→Component Tree→Widget Tree,最终通过 Flutter Engine 完成渲染,实现动态化页面的展示。
    该方案可实现业务的动态化发布,无需发版即可完成业务迭代和问题修复,目前已在多个行业超大型工程的轻量业务场景(如小黑板、家校应用、设备巡检)落地,后续可通过优化内存占用、提升渲染效率,逐步覆盖更多核心业务。
    请添加图片描述

九、落地价值与展望

超大型工程接入 Flutter 并完成规模化落地后,可在研发效率、设计协同、测试成本等方面实现显著提升,核心价值体现在:
研发侧:跨平台技术栈统一,一套代码可运行于多端,需求开发的人力投入减少 50%,大幅提升迭代效率;
设计侧:基于 Flutter 的 UI 渲染一致性,设计侧可聚焦于某一核心平台(如 iOS)的设计,UI 走查效率提升 40%,减少跨端适配成本;
测试侧:Flutter 内部闭环页面可通过单平台测试实现跨平台覆盖,减少多端重复测试的人力投入;
体验侧:通过仿原生组件、性能优化、暗黑模式适配等,实现 Flutter 页面体验媲美原生,保障用户体验一致性。
未来,Flutter 在超大型工程中的发展方向将围绕深度跨端、性能极致、生态完善、动态化成熟展开:
进一步打通移动端与桌面端的能力复用,实现一套代码覆盖全平台,降低跨端开发成本;
针对极致性能场景(如高帧率列表、复杂动画)进行深度优化,突破 Flutter 的性能瓶颈;
完善 Flutter 生态体系,打造更丰富的通用组件库、工具链,提升规模化开发效率;
持续优化 Flutter 动态化方案,解决内存占用、渲染效率等问题,逐步覆盖核心业务,实现动态化与原生体验的平衡。
Flutter 作为高性能的跨平台 UI 框架,为超大型工程的多端开发提供了高效解决方案,而工程化的架构设计、性能优化、生态建设和工具开发,是实现 Flutter 规模化落地的核心保障。未来,随着 Flutter 官方的持续迭代和业内的实践探索,跨全平台 UI 开发将朝着更高效、更优质、更灵活的方向发展。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐