当你开始研究移动开发时,框架和方法的数量很快就让人迷惑。你听到原生应用、React Native、Flutter、Expo、Capacitor、Cordova、Ionic、Tauri Mobile,不太清楚它们到底有什么不同。它们是不是用不同方式做同一件事,还是其中一些在根子上就是不同的架构?
答案是两者都是。构建移动应用主要有三种方式,你听到的大多数框架都属于其中一种。区别在于:什么渲染 UI、什么运行逻辑、两者之间如何对话。
三种方法
这是整体格局:
| 方法 | UI 渲染用 | 逻辑运行在 | 例子 |
|---|---|---|---|
| 原生 | 平台 UI 组件 | 平台语言 | Swift/Kotlin 应用 |
| Web 包装 | WebView(浏览器) | JavaScript | Capacitor、Cordova、Ionic |
| 跨平台 | 各异(原生、画布或 WebView) | JS、Dart 或 Rust | React Native、Flutter、Tauri |
首先要注意的是,"跨平台"不是一种方法。它是一个类别,里面每个框架的 UI 渲染策略都不一样。大部分迷惑就是从这来的。人们听到"跨平台"就以为它指的是一种东西。不是。
一个个来看。
原生应用
这是基线。你用 Swift 或 Objective-C 写 iOS 应用,用 UIKit 或 SwiftUI。你用 Kotlin 或 Java 写 Android 应用,用 Android Views 或 Jetpack Compose。代码编译成平台的原生二进制,UI 是平台自己的 UI 组件,逻辑直接跑在平台运行时上。
iOS: Swift/SwiftUI → 编译 → UIKit/SwiftUI 渲染到屏幕
Android: Kotlin/Compose → 编译 → Android Views/Compose 渲染到屏幕好处是能完整访问平台提供的一切。每个 API、每个 UI 组件、每个平台特有的功能、每个性能优化。没有桥接,没有翻译层,没有抽象。你写的代码就是跑的代码。
代价是你要写两遍应用。一个 iOS 应用和一个 Android 应用是两份完全独立的代码库,用两种不同的语言,两套不同的 UI 框架,两个不同的构建系统,两个不同的部署目标。你要一个功能在两个平台上都有,就要构建两次。
另外两种方法就是从这儿来的。Web 包装和跨平台这两个类别都存在,因为所有东西都写两遍代价太高。
Web 包装
Web 包装方法把一个 Web 应用放进一个原生外壳里。Capacitor(来自 Ionic)和 Cordova 是这里的主要框架。
HTML/CSS/JS → WebView → 原生外壳 → OSUI 是一个 WebView。那是一个浏览器引擎,跟你在应用里打开链接时系统用的同一个。在 iOS 上是 WKWebView,在 Android 上是 Android WebView。你的 JavaScript 跑在那个 WebView 里,就像它跑在一个浏览器标签页里一样。外层的原生外壳提供一个桥接,让你的 JavaScript 能调用平台 API、访问摄像头、读文件系统、显示原生通知等等。
如果你读过 Tauri 介绍,这个结构应该看起来眼熟。Tauri 在桌面上用同样的模式:原生窗口里的 Web UI,带一个通往 OS 的桥接。区别在后端。Capacitor 和 Cordova 桥接到平台的原生语言(Swift 或 Kotlin),每个平台分别写。Tauri 桥接到 Rust,为每个移动目标原生编译。
Web 包装的吸引力很明显。如果你已经有了一个 Web 应用,你可以把它作为移动应用发布,不用重写 UI。在浏览器里跑的同一份 HTML、CSS 和 JavaScript 在 WebView 里跑。你写一个前端,到处都能用。
代价是性能和原生感觉。WebView 是一个浏览器。它把 HTML 渲染到屏幕上,跟浏览器一样。原生 UI 组件更快、更顺滑、感觉像平台,WebView 不总能做到。滚动物理、手势处理、过渡、输入行为在 WebView 里跟在原生 UI 里都不太一样。对有些应用来说无所谓。对另一些来说,这就是全部。
跨平台框架
这里开始有意思了,因为"跨平台"对不同框架意味着不同的事。每个都选了不同的 UI 渲染策略和不同的桥接机制。
React Native
React Native 不渲染到 WebView。你的 React 组件描述的是原生 UI,React Native 为你创建真正的原生 iOS 和 Android 视图。一个 View 在 iOS 上变成 UIView,在 Android 上变成 android.view.View。没有浏览器,没有 HTML,没有 DOM。
React 组件 → JSI 桥接 → 原生 UI 组件 → 屏幕逻辑跑在 JavaScript 里,跑在嵌入应用内部的一个 JavaScript 引擎上。JavaScript 和原生之间的桥接叫 JSI(JavaScript 接口)。它让 JavaScript 能直接调用原生 C++ 对象,这比把所有东西序列化成 JSON 的旧桥接快。
如果你想要对 React Native 和 Expo 的完整深入分析,我写了这里。这篇帖子的简短版本是:UI 是原生的,逻辑是 JavaScript,桥接是 JSI。
Flutter
Flutter 对 UI 用了不同的方法。它不用原生 UI 组件,也不用 WebView。Flutter 有自己的渲染引擎,叫 Impeller(以前叫 Skia),直接画到画布上。屏幕上的每一个像素都是 Flutter 自己的引擎画的。
Flutter 组件 → Dart 运行时 → Impeller 渲染到画布 → 屏幕逻辑跑在 Dart 里,Dart 编译成目标平台的原生 ARM 代码。Dart 和平台 API 之间的桥接叫 Platform Channels。当 Flutter 需要调用一个平台 API,比如摄像头或文件系统,它通过一个 Platform Channel 发消息给原生代码,由原生代码处理请求。
吸引力是一致性。因为 Flutter 画自己的 UI,一个 Flutter 应用在 iOS 和 Android 上外观和行为完全一样。没有"这在 iOS 上看起来像 Android 应用"的问题,因为 UI 用的不是任何一个平台的组件。代价是 Flutter 应用不会自动采用平台的外观和感觉。你想要它感觉原生,得自己构建。
Tauri Mobile
Tauri Mobile 是三者中最新的,它把 Web 包装的 UI 方法和 Rust 后端结合起来,而不是用原生语言后端。
HTML/CSS/JS → WebView → Tauri IPC → Rust → OSUI 是 WebView,跟 Capacitor 和 Cordova 一样。区别在后端。它不是桥接到每个平台分别写的 Swift 或 Kotlin 代码,Tauri 桥接到 Rust,Rust 为每个移动目标原生编译。一份 Rust 代码库处理 iOS 和 Android 两边的原生部分。
这就是单一代码库模式有用的地方。让 Tauri 桌面应用跟 Web 前端共享代码的同一套接口和实现策略也适用于移动。你的服务接口保持不变,你在 Web 和桌面实现旁边加一个移动实现。
共享的模式
这是让整个格局在我脑子里串起来的部分。三个跨平台框架都遵循同一个架构:
UI
|
桥接
|
引擎UI 是展示层。桥接是通信机制。引擎是真正干活的运行时。技术变了,形状不变。
| 框架 | UI | 桥接 | 引擎 |
|---|---|---|---|
| React Native | 原生组件 | JSI | JavaScript 引擎 |
| Flutter | Impeller 画布 | Platform Channels | Dart 运行时 |
| Tauri Mobile | WebView | IPC | Rust |
一旦你看到这个模式,框架之间的区别就不再令人困惑了。它们都是同一个形状的变体。React Native 选原生 UI 和 JSI。Flutter 选自己的画布和 Platform Channels。Tauri 选 WebView 和到 Rust 的 IPC。选择在于你为 UI 层接受哪些权衡,以及你想用哪个桥接机制来触达平台。
如何选择
问题不是哪个框架最好。问题是哪些权衡适合你的应用。
原生(Swift/Kotlin) 如果你需要完整的平台访问、最高的性能,或者一个在每个平台上都完全感觉原生的 UI。代价是维护两份代码库。
React Native 如果你想要由 JavaScript 驱动的原生 UI 组件,而且你已经有 React 团队或想跟 React Web 应用共享逻辑。UI 是原生的,逻辑是 JavaScript,你写一份代码库,同时针对两个平台。
Flutter 如果你想要跨平台像素级完美的一致性,而且你愿意自己构建原生外观和感觉。UI 是 Flutter 自己的渲染,逻辑是 Dart,应用在 iOS 和 Android 上默认看起来一样。
Tauri Mobile 如果你已经有 Web 前端和 Rust 后端,或者你想把 Tauri 桌面应用扩展到移动端而不换技术栈。UI 是 WebView,后端是 Rust,同一份代码库可以同时针对桌面和移动。
Web 包装(Capacitor/Cordova) 如果你已经有 Web 应用,想以最小改动发布到移动端,而且你能接受 WebView 在性能和原生感上的权衡。
更大的图景
大多数跨平台框架,以及大多数现代应用架构总体上,都遵循 UI、桥接、引擎模式。UI 是用户看到的。桥接是 UI 跟引擎对话的方式。引擎是干活的东西。不管你在构建移动应用、桌面应用还是 Web 应用,这个形状都会出现。
技术变了。架构不变。一旦你能看到这个形状,你就能通过问三个问题来评估任何新框架:什么渲染 UI、什么运行逻辑、它们如何通信。其他都是细节。
