先说句得罪人的:2025 年了还在纠结「Flutter 和 React Native 谁性能更好」的,大概率是还没真正上线过跨端项目。这个问题的答案在 2019 年就差不多定了,现在让人掉头发的是另外几件事——热更新能不能用、鸿蒙要不要管、招不招得到人。
我把手上 2022 到 2025 年经手的项目摊开说:三个 Flutter(一个日活 8 万的电商、一个车载工具、一个医疗随访),两个 RN(0.71 上的老项目 + 0.76 的新架构迁移),两个 uni-app(一个政务小程序矩阵、一个生鲜 App),外加一个 KMP 试点(把登录和订单模块下沉到 shared 层)。这些不是调研报告,是真上线、真接客诉的。
空工程的包体积,先给个数垫底
Android arm64 release 单架构,我本地打出来的:Flutter 3.22 空工程 6.1 MB 左右,RN 0.76(关 dev support、开 Hermes、Proguard 全开)8.4 MB 左右,uni-app 走 5+ runtime 的 App 底包 9.7 MB 起,KMP + Compose Multiplatform 能压到 3 MB 出头(但那是个只有一屏 Hello 的东西,接完业务再说)。
iOS 侧反过来了:Flutter 空工程 IPA 大概 18-22 MB,RN 大约 11-13 MB。原因不神秘,Flutter 把 Skia/Impeller 和 Dart 的 AOT 产物都塞进去了,RN 用的是系统自带的 Hermes 加上原生组件。
但这些数字千万别直接拿去选型。同一个框架,版本差两个 minor,设备架构不同,包体积能差一倍。靠谱的做法是把你自己的首页拉进三个空工程各跑一遍——Flutter 用 flutter build apk --analyze-size --target-platform android-arm64,RN 用 npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output ./bundle.js 看原始尺寸,十个工作日能出结论,比看一百篇对比文管用。
第一个真坑:多端的成本,等于补丁数量
所有宣传语都是「一套代码,多端运行」,没人告诉你后半句——每个平台至少一套补丁。我做过最夸张的一个列表页,在 uni-app 里写了四个 template 分支:#ifdef MP-WEIXIN、#ifdef APP-PLUS、#ifdef H5,还有一个 #ifdef MP-ALIPAY。
具体到细节全是琐碎的:iOS 的安全区不是常量,得用 env(safe-area-inset-top) 或者老的 constant();Android 开了 android:windowSoftInputMode="adjustResize" 之后,Flutter 里还得自己用 MediaQuery.of(context).viewInsets.bottom 把输入框顶起来,不然键盘就那么盖着;微信小程序里 textarea 是原生组件,层级永远压在上面,你 z-index 写到 99999 都没用。
我的经验值是:一个中等复杂度的业务 App,跨端能省掉大约 40%-60% 的 UI 工作量,但会多出 15%-25% 的平台适配和调试工作量。省下的那部分能不能盖住多出来的那部分,取决于你有几个端、端之间差异有多大。如果只有 iOS + Android 两端,老实说,原生写可能更快。
第二个真坑:热更新在国内不是技术问题,是合规问题
这条线是很多团队选型的真正原因,也是很多团队翻车的地方。苹果的 App Store 审核指南 2.5.2 和 3.2.2 写得很清楚:不能下载可执行代码,不能通过下载改变主要功能。2017 年 JSPatch 被点名那件事之后,国内做热更新都很谨慎。
React Native 的 CodePush 以前是标配,但微软的 App Center 已经在 2025 年 3 月 31 日正式退役,这块现在得用社区的 react-native-code-push 维护分支,或者自己搭一套更新服务。uni-app 走的是 wgt 包,本质还是 JS 逻辑替换,不碰原生二进制,这条线相对安全,也是它在政企项目里活得不错的原因之一。
如果你把热更新当成刚需,选型空间其实已经被砍掉一大半:Flutter 官方明确不支持(iOS 上根本不可能,Android 也只能靠第三方方案,风险自负)。所以顺序应该是——先问热更新要不要,再问性能。
第三个真坑:混合开发的内存账,没人替你算
纯 Flutter App 没人聊内存,一旦要在原生 App 里嵌几个 Flutter 页面,账就来了。我在一台骁龙 8 Gen 1 的机器上测过,原生 App 基线 180 MB 左右,加一个 FlutterEngine 之后常驻涨到 235-250 MB。
FlutterEngineGroup(Android 从 3.0 起、iOS 从 3.7 起可用)能好一些,它共享 GPU 上下文和 isolate group,多引擎场景能省一大块,但第一颗引擎该占的还是占。预加载引擎能把首帧从 600-800 ms 压到 200-300 ms,代价是这几百毫秒的内存一直挂着。我的做法是只给高频入口预加载,低频页面老老实实冷启动,FlutterEngineCache 里只放一个实例,用完不主动销毁,让系统的内存压力去决定。
RN 这边反而简单,它用原生组件渲染,混排时没有第二套渲染引擎,内存代价主要在 Hermes 的堆上,一般 20-40 MB 量级。这也是为什么有些团队嵌 RN 觉得无感,嵌 Flutter 觉得肉疼。
第四个真坑:鸿蒙这一刀,2024 年才落下来
HarmonyOS NEXT 不再兼容 Android APK 之后,所有跨端框架都被迫重新交卷。Taro 4 和 uni-app 都出了鸿蒙版本,Flutter 有 OpenHarmony SIG 维护的 fork(ohos_flutter),RN 有 react-native-harmony。听着挺齐,成熟度差得远。
uni-app 和 Taro 本来就是 JS/小程序路线,鸿蒙侧的组件映射基本能对齐;Flutter 那个 fork 你就得做好自己填坑的准备,第三方插件生态基本要重来一遍——我随手查了十来个常用的(高德地图、极光推送、扫码、蓝牙打印),能直接用的不到一半。
所以如果项目要覆盖鸿蒙,先把插件清单列出来一个个查官方仓库的 openharmony 分支,别信文档里那句「已支持」。
第五个真坑:RN 新架构迁移,0.76 是分水岭
RN 0.76 把新架构(Fabric 渲染器 + TurboModules + Bridgeless 模式)设成了默认。0.68 就有开关,0.74 开始 Bridgeless 可选,到 0.76 直接默认开。你在 gradle.properties 里能看到 newArchEnabled=true。关掉它,一堆新库用不了;开着它,那批没适配 TurboModule 的老库直接白屏。
报错信息通常长这样:TurboModuleRegistry.getEnforcing(...): 'XXX' could not be found,不痛不痒,你得一个个去翻 issue。我那次迁移,21 个第三方依赖挂了 4 个,其中两个有 PR 没人 merge,最后自己打的 patch。真实建议是先跑 npx react-native doctor,再把 package.json 里所有带原生代码的依赖按最近提交时间排个序,半年没更新的,默认当不适配。
说点我自己的判断
跨端框架真正的分水岭,不是「支持几端」,而是「谁掌握 UI 树的最终解释权」。
Flutter 是自绘的,从 Skia 换到 Impeller 之后更是把像素解释权攥在自己手里,所以你在哪看到的都一样,代价是体积大、跟原生混排时是两套世界。React Native 解释的是组件树,最终渲染还是原生控件,所以体验贴近平台,但平台组件的差异会原封不动漏到你脸上。Capacitor/Cordova 这一类干脆什么都不解释,只有一个 WebView 壳,所以最轻、最灵活,也最不受控。KMP 更极端,它只下沉逻辑层,UI 交给 Compose Multiplatform 或者各平台自己写——Compose Multiplatform 的 iOS 支持到 2025 年才算站住。
这个结构推下来,我的结论有点反直觉:越靠近 UI,跨端的收益越大,但天花板越低;越靠近逻辑,收益越小,但越安全。所以「Flutter 和 KMP 谁更好」这个问题本身就问错了,它们根本不在同一层。
另一个可能被喷的观点:2025 年选型的主要矛盾已经不是性能和包体积,是人和合规。招一个能扛 Flutter 的资深,比招一个能扛 RN 的更难也更贵;但 Flutter 那边 UI 人力复用率确实高,一个人写四端界面不费劲。这笔账得按你自己团队构成算,不是按 GitHub star 算。
如果你明天就要定,按这个顺序走
-
先定端清单和流量权重。小程序占大头,uni-app/Taro 基本没得选;只有 iOS + Android,先算算原生是不是更便宜。
-
再问两个一票否决的问题:要不要热更新?要不要覆盖鸿蒙?这两个没答案之前别去比性能,比了也是白比。
-
然后才做技术验证。拿你自己的首页,不是官方 demo,分别丢进候选框架,测三件事:release 包体积增量、冷启动耗时、内存基线。Flutter 用
flutter run --profile --trace-startup,会在 build 目录吐出start_up_info.json;Android 冷启动用adb shell am start -W -n 包名/.MainActivity看 TotalTime;iOS 用 Instruments 的 App Launch 模板。 -
最后查插件生态。把要用的能力列成一张表(推送、地图、支付、扫码、蓝牙、文件、生物识别),去各自市场按最近更新时间筛一遍。这一步能筛掉一半候选。
写到这儿。我自己的倾向是:业务重、迭代快、团队前端底子好,选 RN 或 uni-app;交互复杂、UI 一致性要求高、团队能做原生,选 Flutter;后端是 Kotlin 而且不打算动 UI,才考虑 KMP。没有银弹,只有你愿意吃哪种苦。