从 React Native 到 Flutter,我们在跨平台开发中踩过的坑和学到的教训

单元测试补习生
2025-06-29 10:58
阅读 246

背景:为什么我们决定做一次技术选型

背景:为什么我们决定做一次技术选型

去年公司决定启动一个全新的 App 项目。我们的目标是快速上线、低成本维护、并支持 iOS 和 Android 双平台。考虑到团队本身有前端背景,第一反应就是用 React Native 来搞这个活儿。

但事情总不会那么顺利。随着项目的推进,我们逐渐发现 RN 的性能瓶颈和原生模块接入上的麻烦。更关键的是,产品需求频繁变更,UI 复杂度不断提升,React Native 的“伪原生”体验开始捉襟见肘。

于是,我牵头重新审视了当时市面上主流的几个跨平台方案:React Native、Flutter、Ionic、Weex,甚至还有 Xamarin(虽然最后没考虑)。我们花了两周时间做了个 PoC(概念验证),最终敲定了用 Flutter 作为新版本的核心开发框架。

这中间经历了不少折腾,也踩了不少坑。今天我就来聊聊这段过程,希望你遇到类似问题时能少走点弯路。


问题描述:旧框架带来的痛

问题描述:旧框架带来的痛

最初我们用 React Native 启动了一个 MVP(最小可行性产品)版本,主要功能包括:

  • 消息推送
  • 图文混排内容展示
  • 视频播放器封装
  • 用户登录系统(带微信/手机号)
  • 地图组件集成

听起来似乎不复杂,但在实际开发过程中,我们遇到了不少头疼的问题:

1. 原生模块对接成本高

比如我们要集成地图SDK,iOS 用的是高德,Android 也一样,但每个平台都要分别封装一层桥接代码,而且容易出错。每次升级 SDK 版本都得小心翼翼地检查 Bridge 的兼容性。

2. UI 细节难还原

设计师给的 Sketch 往往非常细节,RN 的 Flexbox 布局加上默认的字体、padding 等样式,在不同设备上渲染差异很大。尤其是 iOS 和 Android 对某些 CSS 样式的支持程度不一致,导致页面看起来总是差点意思。

3. 性能波动明显

特别是在视频播放场景下,React Native 的 View 层嵌套过多,导致滑动卡顿严重,帧率经常掉到 40fps 以下。而用户反馈最频繁的就是“切换页面的时候有点慢”。

4. 团队协作效率低

前端团队习惯了 JavaScript + CSS 的开发方式,而后端转过来的同事则对 JS 的异步回调机制不太适应。大家在状态管理和生命周期控制方面争论不断,代码越来越难以维护。


解决方案:Flutter 成为我们最终的选择

解决方案:Flutter 成为我们最终的选择

在尝试过 Flutter 的官方文档、Demo 和社区资料后,我觉得它很适合解决上述痛点。Flutter 不仅提供了高性能的自绘引擎,还让 UI 风格统一可控,这对跨平台 App 来说简直是刚需。

我们当时的决策标准是这样的:

维度 React Native Flutter
UI 渲染精度 中等
开发效率 快(前端友好) 中等偏快
原生交互能力 弱(需桥接) 强(Platform Channel)
性能表现 中等(受限于 Bridge)
编译构建耗时 较短 较长
社区生态 成熟 快速成长中
团队学习曲线 陡峭(JS + native) 相对平缓

综合来看,我们最终选择了 Flutter,核心原因在于:

  • 一致性体验强:UI 完全由 Flutter 自己绘制,没有平台差异
  • 可拓展性强:Platform Channels 让与原生通信更加顺畅
  • 性能优秀:Dart+Skia 架构保障流畅渲染
  • 热重载助力开发:极大提升了调试效率

当然,也不是完全没有顾虑。最大的不确定性是我们当时几乎没有人真正做过大型 Flutter 项目,所以我们也准备了详细的技术调研和培训计划。


实践:Flutter 如何解决问题

换框架不是一蹴而就的事。为了稳妥起见,我们决定采用双轨策略:一边保持 RN 的老版本更新,另一边用 Flutter 重写新模块,并在后续逐步迁移。

1. 页面结构和组件复用

Flutter 的 Widget 系统设计得非常好。我们定义了一套自己的“基础组件库”,比如按钮、文本框、图标、TabBar 等,都在一个 common_widgets 包里共享使用。这样不仅减少了重复代码,也让 UI 保持一致。

举个例子,我们自己封装了一个通用的 CustomAppBar 组件:

class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
  final String title;
  final List<Widget> actions;

  const CustomAppBar({Key? key, required this.title, this.actions = const []}) : super(key: key);

  @override
  Size get preferredSize => const Size.fromHeight(kToolbarHeight);

  @override
  Widget build(BuildContext context) {
    return AppBar(
      title: Text(title),
      actions: actions,
    );
  }
}

这种高度定制化的设计方式让我们在后期扩展和风格统一上省了不少劲。

2. 与原生交互优化(通过 Platform Channel)

我们在 Flutter 中通过 MethodChannel 调用原生方法,例如获取设备型号、调用系统相机、处理定位权限等。下面是一个简单调用原生摄像头的例子:

// flutter 端
Future<void> openCamera() async {
  final platform = MethodChannel('com.example.camera');
  try {
    await platform.invokeMethod('openCamera');
  } on PlatformException catch (e) {
    print("Failed to open camera: '${e.message}'.");
  }
}

// Android 端 Kotlin 示例
val channel = MethodChannel(flutterEngine.dartExecutor, "com.example.camera")
channel.setMethodCallHandler { call, result ->
    if (call.method == "openCamera") {
        // 调用系统 Intent 打开相机
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        startActivity(intent)
        result.success(null)
    } else {
        result.notImplemented()
    }
}

注意,这里只是演示代码。实际中需要处理 ActivityResultCallback、上下文传递等问题。

3. 性能优化与内存监控

Flutter 在性能优化方面其实有很多工具可以借助,比如 DevTools 提供了 CPU Profiling、Memory Usage、GPU 分析等功能。

我们遇到一个问题是在首页加载大量图片时,App 内存飙升。后来通过使用 Image.memory() 并配合 CachedNetworkImage 插件解决了大部分资源浪费问题。

dependencies:
  cached_network_image: ^3.5.3
CachedNetworkImage(
  imageUrl: "https://example.com/image.jpg",
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
);

另外,针对一些大图展示页,我们也采用了懒加载和滚动事件监听的方式,避免一次性加载所有资源。


踩坑经验分享

1. 包体积太大

刚开始用 Flutter 时,我们打包出来的一个 release APK 竟然有 30MB!这对于一个刚起步的新 App 来说是灾难性的。

解决方案:

  • 启用 --split-per-abi 参数构建不同架构的包:
flutter build apk --release --split-per-abi
  • 移除不必要的依赖项,使用 flutter pub deps 查看依赖树
  • 使用 ProGuard 或 R8 进行代码压缩

2. 多语言支持做得不好

早期我们手动管理多语言文案,结果每次加一条新翻译都要改一堆文件。后来引入了 flutter_gen_l10n 插件,彻底解放了生产力。

配置如下:

dev_dependencies:
  flutter_gen_l10n: ^0.13.1

flutter_gen_l10n:
  arb-file: app_en.arb
  arb-directory: lib/l10n
  use-only-english-default: true
  generate: true

然后直接引用:

Text(S.current.welcome_message);

3. 发布应用市场时的各种限制

发布到 Google Play 和 Apple App Store 的时候也遇到不少坑:

Android:

  • 注意启用 multidex 支持(如果你的方法数超了65536)
  • 使用 App Bundle 替代 APK 减少分发体积
  • 注意 targetSdkVersion 升级到最新(当前为 34)

iOS:

  • Flutter 项目的 Info.plist 文件要手动生成
  • 苹果 App Store 上架前必须上传元数据和截图,流程较为繁琐
  • 使用 Xcode 打包时要注意自动签名还是手动签名模式

效果总结:收益显著

切换成 Flutter 后,整体项目的维护成本大幅降低,开发效率提升约 40%,并且 UI 一致性得到了极大增强。

以下是几个关键指标的变化:

指标 RN 时期 Flutter 时期
平均帧率 ~45 fps ~58 fps
首次加载时间 3s+ 2s 内
新增功能开发周期 7~10 天 4~6 天
应用崩溃率 0.6% 0.1% 以下
多语言适配支持速度 手动维护,易遗漏 自动生成,维护高效

最关键的是,产品经理对新版本的 UI 交付效果非常满意 —— 她再也不用纠结某个按钮“在 iPhone 上怎么变样了”。


经验分享:给读者的一些建议

结合这次经验,我想给正在做技术选型的朋友几点建议:

1. 明确你的业务需求,再决定是否选择跨平台框架

如果你的应用逻辑复杂、性能敏感,或重度依赖原生 API,那可能更适合单独开发 iOS 和 Android。

但如果你们想用一份代码覆盖多个平台,又不想牺牲太多用户体验,Flutter 是目前最靠谱的选择之一。

2. 团队转型期要有过渡方案

如果你们现在已经在用 React Native,别急着推倒重来。可以用 Hybrid 的方式逐步替换模块,确保上线节奏不受影响。

3. 投资基础设施建设

无论你选择哪种框架,都应该尽早搭建好 CI/CD 流水线、自动化测试、国际化支持、异常监控体系等基础设施,这些将是你后期持续交付的关键支撑。

4. 拥抱变化,但不要盲目跟风

Flutter 虽好,但它也有局限性,比如桌面端和 Web 端还不太成熟,Web 端更是因为性能问题只能做轻量展示。

所以一定要根据你的产品定位和目标平台来做技术选型,而不是单纯去追风口。


结语

技术选型从来都不是一件轻松的事情。我们团队在这个过程中也经历了迷茫、质疑和反复试错。但我始终相信一点:没有绝对正确的方案,只有最适合当下的解法

希望这篇文章能帮你厘清思路,找到真正适合你项目的跨平台方案。如果你也走过类似的路,欢迎留言交流,一起成长 😊

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝