iOS开发基础117-Hybrid 混合开发:从底层原理到工程实践的完整指南

Hybrid 混合开发:从底层原理到工程实践的完整指南

本文站在 iOS 开发者视角,用 UIKit、WKWebView、JavaScriptCore 等熟悉概念,系统拆解 Hybrid 混合开发的本质、四类主流方案的底层渲染原理与通信机制、跨平台模块嵌入原生 App 的具体方式、五大工程挑战,以及选型决策与未来趋势。


一、Hybrid 的本质:不是非黑即白,而是一条光谱

1.1 一个常见的误区

很多人对跨平台开发的认知是二元对立的:要么纯原生(Swift/OC + UIKit),要么用跨平台框架写整个 App。但实际上,业界绝大多数 App 都是混合体。

抖音、小红书、美团、京东、支付宝的 iOS App 里,既有大量原生代码,也有 H5 页面、小程序、RN 页面甚至 Flutter 模块。这就是 Hybrid 混合开发——以原生 App 为主体,在特定页面或模块中使用跨平台技术。

1.2 混合开发的定义

Hybrid 的核心定义:在同一个 App 中,同时存在原生代码和非原生代码(H5/JS/Dart),二者通过某种通信机制协作,共同构成完整的用户体验。

用 iOS 开发者能理解的话说:AppDelegate、导航栈、底层架构是原生的,但某个 UIViewController 的 view 可能是一个 WKWebView(H5)、一个 RCTRootView(RN)、一个 FlutterView(Flutter),或者一个小程序容器 View。

1.3 为什么需要混合开发

纯原生体验最好、性能最高,但有三个天然短板:

  • 发版慢:iOS 必须经过 App Store 审核,紧急修复或运营活动无法即时上线。
  • 成本高:iOS 和 Android 各写一套,人力成本翻倍。
  • 灵活性差:运营活动页、营销页面频繁变更,原生迭代周期跟不上。

混合开发就是为了在保留原生性能体验的同时,获得跨平台复用和动态化能力。它不是取代原生,而是原生的补充。


二、主流 Hybrid 方案分类与底层原理

所有跨平台方案,本质上都是在"渲染方式"这个维度上做不同取舍:

类型 代表方案 渲染方式 一句话本质
WebView 容器 Cordova、H5 WebKit DOM 渲染 全屏 WKWebView 套个原生壳
小程序 微信/支付宝小程序 WebView + 原生组件混合 双线程隔离的 WebView + 部分原生组件覆盖
JS 驱动原生渲染 React Native、Weex 原生 UIKit 组件渲染 JS 发指令,原生端创建 UIView
自绘引擎 Flutter Skia/Impeller GPU 绘制 整个 App 只有一个 UIView,里面全是 GPU 绘制的像素

2.1 WebView 容器类:Cordova / H5

架构与原理

最朴素的混合方案。整个 UI 就是一个全屏 WKWebView,页面渲染、布局、交互全走 WebKit,和 Safari 打开网页没有本质区别。

原生能力(相机、GPS、文件)通过 JSBridge 暴露给 JS。iOS 端本质就是 WKScriptMessageHandler:JS 调用 window.webkit.messageHandlers.xxx.postMessage(),原生收到后执行对应代码,结果再通过 evaluateJavaScript 回调给 JS。

iOS 开发者类比

就像写了一个 App,只有一个 ViewController,里面放了一个全屏 WKWebView,然后用 WKUserContentController 注册了一堆 JS 方法。Cordova 就是把这套东西标准化、插件化了。

性能瓶颈

  • DOM 渲染慢、列表滚动卡顿、动画掉帧——本质是网页。
  • WebView 内存占用高,多个 WebView 同时存在时内存压力大。
  • 首次加载白屏时间长(HTML/CSS/JS 都要下载和解析)。

2.2 小程序:双线程 + 混合渲染

为什么要双线程

小程序的核心约束:不允许 JS 直接操作 DOM(防止恶意代码篡改页面、绕过审核)。所以把逻辑和视图拆开,跑在两个独立线程里:

  • 逻辑层:JS 代码跑在独立的 JavaScriptCore(iOS)中,没有 window、document,不能操作 DOM。只能拿到框架注入的 App()、Page()、wx.xxx 等 API。
  • 视图层:每个页面是一个独立的 WKWebView,WXML 被编译成 JS 函数,接收数据后生成虚拟 DOM,再渲染成真实 DOM。

数据通信机制

调用 setData({...}) 时,数据被序列化为 JSON,通过 Native 中间层从逻辑层的 JSCore 转发到视图层的 WebView,视图层拿到数据后更新 DOM。

不是视图层"去取"数据,而是逻辑层主动"推送"数据。这个推送是跨线程 + JSON 序列化的——这就是为什么小程序优化指南反复强调"不要频繁 setData、不要传大数据"。

原生组件

map、video、canvas、input 等组件不是 DOM,而是原生 View 覆盖在 WebView 上面(后来演进为"同层渲染",把原生组件嵌入 WebView 的合成层)。这就是为什么这些组件层级总是最高、样式限制多。

补充:微信小程序在 2023 年推出了 Skyline 渲染引擎,采用类 Flutter 的自绘方案(基于 Skia),不再依赖 WebView DOM 渲染,性能大幅提升,支持更复杂的动画和自定义渲染。这是小程序从"WebView 渲染"向"自绘引擎"演进的信号。

iOS 开发者类比

  • 逻辑层 ≈ 一个后台运行的 JSContext,没有 DOM,只有框架注入的 API。
  • 视图层 ≈ 每个页面对应一个 WKWebView,用 DOM 渲染页面。
  • Native 中间层 ≈ 一个 dispatcher,把 JSContext 里 setData 的数据通过 WKScriptMessageHandler 发给对应 WebView。
  • 原生组件 ≈ 在 WKWebView 上 addSubview 了一个 MKMapView / AVPlayerLayer。

2.3 JS 驱动原生渲染:React Native / Weex

核心思想

用 React/Vue(JS)写业务逻辑和组件声明,但最终渲染出来的是真正的原生组件(iOS 上是 UIView、UILabel、UIImageView 等),不是 DOM。

React Native 旧架构(Bridge)

旧架构有三个线程:

  • JS 线程(JavaScriptCore):跑 React 代码,生成虚拟 DOM 树。但这个虚拟 DOM 不渲染成 HTML,而是通过 Bridge 发指令给原生端:"创建一个 View,id=1,背景色红色"。
  • Shadow 线程:用 Yoga(一个 C 写的 Flexbox 布局引擎)计算每个节点的 frame。
  • 主线程:根据指令创建 UIView 子类(RCTView 就是 UIView 子类),设置 frame、backgroundColor 等属性。

JS 和原生之间通过异步消息队列通信,所有消息都要序列化为 JSON——这是旧架构最大的性能瓶颈。

iOS 上的 JSCore 限制:iOS 系统的 JavaScriptCore 不支持 JIT(即时编译),只能解释执行,JS 性能比 Android(V8 引擎支持 JIT)差。RN 新架构引入了 Hermes 引擎(支持字节码预编译,优化启动和内存),在 iOS 上也能获得更好的 JS 执行性能。

React Native 新架构(Fabric + JSI)

新架构的核心变化是引入了 JSI(JavaScript Interface):JS 可以直接持有 C++ 对象的引用,同步调用方法,不需要序列化 JSON、不需要异步排队。

用 iOS 开发类比:旧架构是 dispatch_async 传字典,新架构是直接拿到对象指针调方法。

配套的 Fabric 渲染器支持同步 UI 更新,TurboModules 支持原生模块按需加载,启动更快。

补充:RN 新架构中,RCTBridge 正在被 RCTHost 替代。RCTHost 是新架构的顶层入口,管理 Fabric 渲染器、TurboModules、JSI 运行时,嵌入原生 App 时逐步从 RCTBridge 迁移到 RCTHost。

Weex

Weex 和 RN 是同一类方案,区别在于:

  • JS 框架:Weex 用 Vue,RN 用 React。
  • 布局引擎:Weex 用自研布局引擎(Flexbox 子集),RN 用 Yoga。
  • 组件模型:Weex 更轻量,组件按需注册。

本质都是"JS 远程操控原生 UIView 层级"。但 Weex 社区已基本停止维护,新项目不建议选用。

关键细节:布局不用 Auto Layout

RN/Weex 不使用 UIKit 的 Auto Layout。它们用跨平台的 Flexbox 布局引擎在 Shadow 线程计算好每个组件的 frame,然后直接把 frame 赋值给 UIView。你在 RN 里写的 flex: 1、justifyContent: 'center',最终是 Yoga 算出来的 CGRect,不是 NSLayoutConstraint。

2.4 自绘引擎:Flutter

完全不同的物种

Flutter 不使用原生组件,也不使用 WebView。它自己实现了一套渲染引擎,把所有 UI 都自己画到 GPU 上。整个 Flutter App 在 iOS 上本质就是一个全屏的 UIView(FlutterView),里面的按钮、文字、列表全是这个 View 用 Metal/OpenGL 画出来的像素。

三棵树渲染模型

Flutter 最核心的设计是三棵树:

  • Widget 树:你写的 Container、Text、Column 都是 Widget。Widget 是不可变的配置对象(类似 SwiftUI 的 View)。每次 setState 都会重新 build 一棵新的 Widget 树。
  • Element 树:Widget 只是配置,Element 是它实例化后的节点,持有生命周期、位置、父子关系。Element 会对比新老 Widget(canUpdate),决定是更新还是重建。
  • RenderObject 树:真正负责布局(layout)和绘制(paint)的节点。每个 RenderObject 有 performLayout()(算 size/position)和 paint()(画到 Canvas 上)。

最终,RenderObject 的 paint 结果通过图形引擎绘制到 GPU Surface 上。

渲染引擎:Flutter 早期默认用 Skia(2D 图形库)。Flutter 3.10(2023.5)起,iOS 平台默认启用 Impeller 渲染引擎,替代 Skia。Impeller 预编译 shader(着色器),解决了 Skia 在首次绘制时 shader 编译导致的卡顿(jank),动画性能更稳定。Android 平台 Impeller 仍在推进中。

Dart 执行模式

  • 开发期(JIT):Dart VM 即时编译,支持热重载(Hot Reload)——改代码后秒级刷新。
  • 发布期(AOT):Dart 代码被提前编译成机器码(ARM64 汇编),直接运行,没有解释器开销。

这是 Flutter 性能接近原生的关键原因之一。对比:RN/Weex 的 JS 是解释执行或 JIT(iOS 上 JSCore 不支持 JIT,只能解释),Flutter 的 Dart 是 AOT 机器码。

平台通信:Platform Channel

Flutter 和原生之间通过 MethodChannel / EventChannel 通信,消息是异步的,通过 StandardMessageCodec 序列化为二进制。类比 iOS:类似用 NotificationCenter 发通知传数据,或者两个线程之间用 dispatch_async 传 block。

iOS 开发者类比

想象你用 Metal + Core Text 自己写了一个 UI 框架:整个 App 只有一个 UIViewController,里面放一个全屏 UIView,这个 View 的 layer 是 CAMetalLayer。你自己实现了 Button、Text、ListView 等组件,每个组件知道自己的 frame,并在 draw 时用 Metal 画出来。你自己实现了一套 Flexbox 布局算法,自己处理手势事件分发。你的业务代码用 Dart 写,AOT 编译成机器码直接跑。这就是 Flutter。


三、如何把跨平台模块嵌入原生 App

RN、Flutter、Weex 都官方支持嵌入原生 App;H5 直接用 WKWebView;小程序需要第三方容器 SDK。

3.1 React Native 嵌入

核心是两个类:RCTBridge(管理 JS 运行时和通信)和 RCTRootView(UIView 子类,承载 RN 界面)。

// AppDelegate 中全局共享一个 Bridge
@property (nonatomic, strong) RCTBridge *bridge;

self.bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];

// 在需要的 ViewController 中创建 RCTRootView
NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:self.bridge
                                                    moduleName:@"MyRNModule"
                                             initialProperties:@{@"userId": @"123"}];
rootView.frame = self.view.bounds;
[self.view addSubview:rootView];

关键点:

  • RCTRootView 就是一个 UIView 子类,可以加到任何 UIViewController 中。
  • initialProperties 是原生传给 RN 的初始数据(类似 initWithParams:)。
  • 一个 App 内可以有多个 RCTRootView,但共享一个 RCTBridge 性能最好。
  • 通过 CocoaPods 引入依赖,包体积增加约 3-5MB(实际取决于引入的原生模块,可能更大)。

3.2 Flutter 嵌入(Add-to-App)

核心是两个类:FlutterEngine(Dart 运行时)和 FlutterViewController / FlutterView(承载 Flutter 界面)。

// AppDelegate 中全局共享一个 FlutterEngine
@property (nonatomic, strong) FlutterEngine *flutterEngine;

self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my_engine"];
[self.flutterEngine runWithEntrypoint:nil];

// 方式一:push 一个 FlutterViewController
FlutterViewController *flutterVC = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
[self.navigationController pushViewController:flutterVC animated:YES];

// 方式二:部分嵌入(一个 VC 里既有原生 View 又有 Flutter View)
FlutterView *flutterView = [[FlutterView alloc] initWithEngine:self.flutterEngine];
flutterView.frame = CGRectMake(0, 0, 320, 400);
[self.view addSubview:flutterView];

FlutterEngine 初始化有开销(首次约几百 ms),建议预初始化、全局共享一个,不要每次进入页面都创建。多个 Flutter 页面如果各自创建 Engine,内存会暴涨。

3.3 Weex 嵌入

核心是 WXSDKInstance:

[WXSDKEngine initSDKEnvironment];

WXSDKInstance *instance = [[WXSDKInstance alloc] init];
instance.frame = self.view.bounds;
instance.viewController = self;

[instance renderWithURL:url options:@{@"bundleUrl": url.absoluteString} data:data];

[self.view addSubview:instance.view];

如前所述,Weex 社区已基本停止维护,老项目可继续维护,新项目不建议接入。

3.4 H5 / WKWebView 嵌入

最简单的方式,直接用 WKWebView:

WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
WKUserContentController *userContent = [[WKUserContentController alloc] init];
[userContent addScriptMessageHandler:self name:@"nativeBridge"];
config.userContentController = userContent;

WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com"]]];
[self.view addSubview:webView];

如果只是部分页面用 H5,自己用 WKScriptMessageHandler 写个桥比引入 Cordova 更轻量。

JSBridge 完整实现示例:

@interface WebViewController () <WKScriptMessageHandler>
@property (nonatomic, strong) WKWebView *webView;
@end

@implementation WebViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    [config.userContentController addScriptMessageHandler:self name:@"nativeBridge"];
    self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
    [self.view addSubview:self.webView];
    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com"]]];
}

// JS 调用 OC
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:@"nativeBridge"]) {
        NSDictionary *msg = message.body;
        NSString *action = msg[@"action"];
        NSString *callbackId = msg[@"callbackId"];
        
        if ([action isEqualToString:@"getUserInfo"]) {
            NSDictionary *userInfo = @{@"userId": @"123", @"name": @"John"};
            [self callJSCallback:callbackId data:userInfo];
        }
    }
}

// OC 回调 JS(将结果传回 JS)
- (void)callJSCallback:(NSString *)callbackId data:(NSDictionary *)data {
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:0 error:nil];
    NSString *jsonStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    NSString *js = [NSString stringWithFormat:@"window.JSBridge.callback('%@', %@);", callbackId, jsonStr];
    [self.webView evaluateJavaScript:js completionHandler:nil];
}

- (void)dealloc {
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"nativeBridge"];
}

@end

JS 端:

// JS 调用 OC
window.JSBridge = {
    callbacks: {},
    call(action, data, callback) {
        const callbackId = 'cb_' + Date.now();
        if (callback) this.callbacks[callbackId] = callback;
        window.webkit.messageHandlers.nativeBridge.postMessage({
            action, data, callbackId
        });
    },
    callback(callbackId, data) {
        const cb = this.callbacks[callbackId];
        if (cb) {
            cb(data);
            delete this.callbacks[callbackId];
        }
    }
};

// 使用
window.JSBridge.call('getUserInfo', {}, (data) => {
    console.log('用户信息:', data);
});

3.5 小程序嵌入

微信小程序本身不能嵌入到你的 App 中——微信没有开放这个能力,小程序只能在微信内运行。

如果想让你的 App 具备运行小程序的能力,有两种方案:

  • 第三方小程序容器 SDK(如 FinClip、mPaaS 等):引入 SDK 后,你的 App 就能运行小程序格式的代码,支持独立开发、独立发版(热更新),不需要经过 App Store 审核。但这是第三方方案,兼容性、性能、成本都要评估。
  • 自研小程序运行时:一些大厂(支付宝、百度、字节)在自己的 App 内实现了小程序运行时,本质就是一个 JSCore 跑逻辑层 + 多个 WKWebView 跑视图层 + 一套原生组件库 + 一套打包下发机制。开发成本极高,一般公司不会自己做。

四、混合开发的五大工程挑战

"能嵌入"只是第一步。真正的成本在于混合开发带来的工程复杂度。

4.1 混合栈导航管理

最头疼的问题。考虑场景:原生页面 A → push 一个 RN 页面 B → RN 页面 B 内部又 push 了一个 RN 路由 C → 用户点返回,应该回到 B 还是 A?导航栏是原生的还是 RN 自己的?侧滑返回手势是否生效?

每个方案都有对应的社区库(如 RN 的 react-native-navigation、Flutter 的 flutter_boost),但都不完美。原生和跨平台页面的导航栈同步、生命周期同步、手势冲突,需要大量工程投入。

4.2 数据通信

  • 入参传递:原生页面跳转到跨平台页面时怎么传参?(一般通过 initialProperties / initialRoute)
  • 回传数据:跨平台页面怎么通知原生页面刷新数据?(一般通过事件总线 / 回调 / 原生模块)
  • 共享状态:跨平台模块怎么访问原生的用户信息、Token、缓存?(一般通过原生模块暴露 API)

这些通信机制都需要封装和规范,否则代码会混乱不堪。

4.3 生命周期同步

跨平台页面的 viewDidAppear / viewDidDisappear 怎么和原生 VC 生命周期同步?App 退到后台、回到前台时,跨平台模块怎么收到通知?内存警告时怎么处理?

这些生命周期事件需要原生层主动转发给跨平台层,跨平台框架才能正确响应。

4.4 包体积与性能

方案 包体积增量 性能特点
H5 / WKWebView 几乎为 0(系统自带) 首次加载慢,DOM 渲染性能差,内存占用高
React Native 约 3-8MB 原生渲染性能好,旧架构 Bridge 通信有瓶颈,新架构大幅改善
Flutter 约 5-15MB 自绘引擎性能最高,Engine 初始化有开销,内存占用较高
小程序容器 约 3-8MB(SDK 本身) WebView 渲染 + 原生组件,性能介于 H5 和 RN 之间

包体积数据为粗略估算,实际取决于引入的原生模块数量和引擎配置。RN 引入较多原生模块时可能超过 8MB,Flutter 精简引擎配置可压缩到 5MB 左右。

4.5 调试与排查

  • 原生代码和跨平台代码的断点怎么同时调试?
  • 线上 Crash 是原生的还是 JS/Dart 的?怎么区分和符号化?
  • 性能监控(FPS、内存、启动时间)怎么覆盖跨平台页面?
  • 跨平台页面的网络请求怎么抓包和排查?

这些都需要额外的工具链和监控体系支撑。


五、选型决策指南

5.1 场景对比

业务场景 推荐方案 原因
运营活动页、营销页面(频繁变更) H5 + WKWebView 最轻量,支持热更新,开发成本低,Web 开发者充足
多个业务模块跨端复用,对性能有要求 React Native 嵌入最成熟,原生渲染性能好,社区活跃,生态完善
对 UI 一致性和动画性能要求极高,团队有 Dart 能力 Flutter 自绘引擎性能最好,UI 一致性最高,但混合栈成本高
需要第三方开发者在你的 App 内开发轻应用 小程序容器 SDK 唯一可行方案,支持热更新和独立发版,但成本高
老项目已经用了 Weex 继续维护,逐步迁移 社区已死,不建议新模块接入,逐步迁移到 RN 或 Flutter
核心业务页面、对性能和体验要求极致 纯原生 没有任何方案能超越原生的性能和系统集成度

5.2 决策原则

  • 原生优先:核心业务页面、对性能和体验要求极致的页面,永远优先原生。跨平台方案是补充,不是替代。
  • 动态化需求驱动:如果一个页面不需要频繁变更、不需要跨端复用,就没有必要用跨平台方案。
  • 评估团队能力:RN 需要前端 + 原生协作,Flutter 需要 Dart 能力,H5 需要前端能力。选型要匹配团队现状。
  • 考虑长期维护:引入一个跨平台方案意味着长期维护成本(框架升级、问题排查、性能优化),不是一次性投入。
  • 从小范围试点开始:不要一上来就把核心模块用跨平台方案重写。先从一个非核心页面试点,验证开发效率、性能、稳定性后再逐步推广。

六、未来趋势

6.1 大前端融合

随着 RN 新架构(JSI/Fabric)和 Flutter 的成熟,跨平台方案的性能越来越接近原生。未来趋势是"大前端"——前端开发者不再局限于 Web,而是用一套技术栈覆盖 Web、iOS、Android、桌面端甚至嵌入式设备。

6.2 动态化与原生的平衡

iOS 平台对动态化的限制(苹果禁止应用下发可执行代码)使得纯 JS 方案(RN/Weex/小程序)在 iOS 上有天然优势——因为 JS 是解释执行的,不需要审核。而 Flutter 的 AOT 编译意味着它无法像 RN 那样通过热更新 JS bundle 来动态发布,这是 Flutter 在混合开发场景下的一个短板。

苹果审核政策细节:苹果《App Store 审核指南》2.5.2 条款禁止应用通过下发可执行代码改变行为,但明确豁免了"在应用沙盒内运行的 JavaScript"。因此 RN/小程序的 JS bundle 热更新是合规的;而 Flutter 的 Dart AOT 机器码无法热更新(下发机器码违反 2.5.2)。这也是国内大厂(阿里、美团)更倾向 RN/小程序方案的原因之一。

6.3 小程序生态的扩展

小程序作为一种"轻应用"形态,正在从微信扩展到更多平台。各大超级 App(支付宝、百度、抖音、QQ)都在建设自己的小程序生态,小程序容器 SDK 也在逐渐成熟。未来,小程序可能成为混合开发的重要形态之一。同时,微信 Skyline 自绘引擎的推出,也标志着小程序在渲染性能上向 Flutter 看齐。


七、Swift 版本对照

WKWebView + JSBridge(Swift)

import WebKit

class WebViewController: UIViewController, WKScriptMessageHandler {
    var webView: WKWebView!
    var callbacks: [String: (Any) -> Void] = [:]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let config = WKWebViewConfiguration()
        config.userContentController.add(self, name: "nativeBridge")
        webView = WKWebView(frame: view.bounds, configuration: config)
        view.addSubview(webView)
        webView.load(URLRequest(url: URL(string: "https://example.com")!))
    }
    
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard message.name == "nativeBridge",
              let msg = message.body as? [String: Any],
              let action = msg["action"] as? String,
              let callbackId = msg["callbackId"] as? String else { return }
        
        if action == "getUserInfo" {
            let userInfo: [String: Any] = ["userId": "123", "name": "John"]
            callJSCallback(callbackId, data: userInfo)
        }
    }
    
    func callJSCallback(_ callbackId: String, data: Any) {
        guard let jsonData = try? JSONSerialization.data(withJSONObject: data),
              let jsonStr = String(data: jsonData, encoding: .utf8) else { return }
        let js = "window.JSBridge.callback('\(callbackId)', \(jsonStr));"
        webView.evaluateJavaScript(js)
    }
    
    deinit {
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "nativeBridge")
    }
}

Flutter 嵌入(Swift)

import Flutter

// AppDelegate 中共享 FlutterEngine
let flutterEngine = FlutterEngine(name: "my_engine")
flutterEngine.run()

// push FlutterViewController
let flutterVC = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
navigationController?.pushViewController(flutterVC, animated: true)

React Native 嵌入(Swift)

// 共享 RCTBridge
let bridge = RCTBridge(delegate: self, launchOptions: nil)

// 创建 RCTRootView
let rootView = RCTRootView(bridge: bridge!, moduleName: "MyRNModule", initialProperties: ["userId": "123"])
rootView.frame = view.bounds
view.addSubview(rootView)

Flutter Platform Channel(Swift 端)

let controller = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "com.example/native", binaryMessenger: controller.binaryMessenger)

channel.setMethodCallHandler { call, result in
    if call.method == "getUserInfo" {
        result(["userId": "123", "name": "John"])
    } else {
        result(FlutterMethodNotImplemented)
    }
}

八、总结

  • Hybrid 本质:不是非黑即白,而是一条光谱。以原生 App 为主体,特定页面/模块使用跨平台技术。核心是原生代码与非原生代码(H5/JS/Dart)通过通信机制协作。
  • 四类方案:① WebView 容器(Cordova/H5,DOM 渲染,性能差但最轻量);② 小程序(双线程 JSCore + WebView,setData 跨线程 JSON 序列化,原生组件覆盖/同层渲染,微信 Skyline 向自绘演进);③ JS 驱动原生渲染(RN/Weex,JS 发指令原生创建 UIView,旧架构 Bridge JSON 瓶颈,新架构 JSI 同步调用,布局用 Yoga 算 frame 不用 Auto Layout,iOS JSCore 无 JIT,Hermes 优化);④ 自绘引擎(Flutter,三棵树 Widget/Element/RenderObject,Skia/Impeller GPU 绘制,Dart JIT/AOT,Platform Channel 通信,性能最接近原生)。
  • 嵌入原生:RN 用 RCTBridge + RCTRootView(新架构 RCTHost),Flutter 用 FlutterEngine + FlutterViewController/FlutterView(Add-to-App),Weex 用 WXSDKInstance,H5 直接 WKWebView + WKScriptMessageHandler 写 JSBridge,小程序用第三方容器 SDK(FinClip/mPaaS)或自研。
  • 五大工程挑战:混合栈导航同步、数据通信规范、生命周期同步、包体积与性能、调试与监控体系。
  • 选型原则:原生优先、动态化需求驱动、评估团队能力、考虑长期维护、从小范围试点。运营活动页用 H5,跨端复用模块用 RN,UI 一致性/动画要求高用 Flutter,轻应用生态用小程序容器,核心页面纯原生。
  • 动态化政策:苹果 2.5.2 禁止下发可执行代码但豁免 JS,因此 RN/小程序 JS bundle 热更新合规,Flutter AOT 机器码无法热更新。
  • 未来趋势:大前端融合、动态化与原生平衡、小程序生态扩展(含 Skyline 自绘演进)。

posted @ 2024-07-17 13:39  Mr.陳  阅读(330)  评论(0)    收藏  举报