在当今快速发展的移动应用开发领域,开发者们始终在寻求一种能够平衡开发效率、跨平台能力和用户体验的完美方案。原生开发性能卓越,但双平台(iOS/Android)开发成本高昂;Web 技术迭代迅速、生态繁荣,却在原生能力调用和离线体验上有所欠缺。
本项目 flutter-web-interaction 提供了一种将两者优点相结合的混合应用(Hybrid App)解决方案。它以 Flutter 作为高性能的原生容器,以 Web 作为灵活的 UI 界面,并在此之上构建了一套完整、高效且功能强大的双向通信机制。这种模式尤其适用于需要快速迭代 UI、业务逻辑多变,同时又依赖部分原生能力的场景,如营销活动页、内容型应用、内部工具等。
本文将从架构设计、通信原理、代码实现等多个维度,深度解析该项目,为您揭示 Flutter 与 Web 混合开发的无限可能。
本项目的核心架构遵循“职责分离”原则,将原生能力与 UI 展现清晰地解耦。
flutter_app
- 原生容器层 (The “Shell”):
flutter_inappwebview
。这不仅是一个简单的 WebView,它为 Flutter 和 Web 之间架起了一座功能丰富的桥梁。web
项目。image_picker
, printing
)封装成统一的 Dart 接口。web
- UI 表现层 (The “View”):
<script setup>
)、Vant 4 (UI)、UnoCSS (CSS)、vConsole (调试)。request.ts
) 调用 Flutter 暴露的接口。window.addEventListener
接收来自 Flutter 的主动推送。graph TD
subgraph "用户设备"
subgraph "Flutter App (原生容器层)"
A[InAppWebView]
B[原生能力封装 <br/> (image_picker, printing, etc.)]
C[生命周期监听 <br/> (AppLifecycleState)]
end
subgraph "Web App (UI表现层)"
D[Vue 3 / Vant UI]
E[JavaScript Bridge <br/> (request.ts)]
end
end
D -- 用户操作 --> E
E -- 1. "拉"模式: 调用原生API --> A
A -- JS Handler --> B
B -- Dart/Native --> F[相机/相册/打印机]
C -- 2. "推"模式: 主动推送事件 --> A
A -- postWebMessage --> E
B -- 返回结果 --> A
A -- evaluateJavascript --> E
一个混合应用的成败,关键在于其原生与 Web 之间的通信是否顺畅、高效。本项目实现了两种通信方式,构成了完整的双向数据流。
这是最核心的通信方式。Web 端像调用后端 API 一样,异步请求 Flutter 提供的原生能力。
InAppWebView
创建时,通过 addJavaScriptHandler
注册一个全局的 JS 处理器,命名为 FlutterApp
。request.ts
中,serverApi
函数是关键。它将请求参数(method
, params
)包装起来,并为每个请求生成一个唯一的 callbackId
。这个 ID 用于在 Flutter 处理完毕后,能准确地回调到 Web 端的特定 Promise。window.flutter_inappwebview.callHandler('FlutterApp', message)
,将封装好的 message
对象发送出去。FlutterApp
处理器接收到 message
对象。在 bridge.dart
的 handerWebMessage
方法中,通过一个 switch
语句,根据 message
中的 method
字段,将请求分发到不同的原生功能实现模块(如 common_utils.dart
中的 chooseImage
)。callbackId
一同包装,通过 webViewController.evaluateJavascript()
执行一段预设的全局 JS 函数(如 window.appJSBridge.callback(result)
)。callbackId
找到对应的 Promise,并调用 resolve
或 reject
,从而完成整个异步调用闭环。sequenceDiagram
participant Web as Web App (Vue)
participant Bridge as JS Bridge (flutter_inappwebview)
participant Flutter as Flutter App (Dart)
participant Native as 原生功能 (SDK)
Web->>Bridge: callHandler('FlutterApp', {method, params, callbackId})
Bridge->>Flutter: 触发 addJavaScriptHandler 回调
Flutter->>Flutter: handerWebMessage: 根据 method 分发
Flutter->>Native: 调用原生功能 (e.g., ImagePicker.pickImage)
Native-->>Flutter: 返回图片数据 (Uint8List)
Flutter->>Bridge: evaluateJavascript('window.appJSBridge.callback({callbackId, data})')
Bridge-->>Web: 执行 JS,找到对应 Promise 并 resolve(data)
当应用状态的改变源于原生层面时,需要主动通知 Web。
WidgetsBindingObserver
监听 AppLifecycleState
(paused
, resumed
等)。webViewController.postWebMessage()
。这个方法会向 Web 页面的 window
对象派发一个标准的 MessageEvent
。message
事件 (window.addEventListener('message', ...)
). 即可捕获到 Flutter 推送过来的数据。这种方式简单直接,非常适合用于状态同步、事件通知等场景。
让我们以“从相册选择图片”为例,完整地走一遍代码流程。
用户点击 (Web):
/web/src/views/index.vue
<van-button @click="chooseImage('')">从相册选择</van-button>
const chooseImage = (source = 'camera') => {
serverApi({ method: 'chooseImage', params: { source } }) // 触发调用
.then(res => {
// 步骤 7: 接收到Flutter返回的数据并处理
const blob = new Blob([new Uint8Array(res.bytes)], { type: getMimeType(res.name) });
imageUrl.value = URL.createObjectURL(blob);
});
}
JS Bridge 发送请求 (Web):
/web/src/utils/request.ts
// serverApi 内部调用 callAppMethod, callAppMethod 内部...
window.flutter_inappwebview.callHandler('FlutterApp', {
method: 'chooseImage',
params: { source: '' },
callbackId: 'uuid-1234' // 假设生成了一个唯一ID
});
Handler 接收与分发 (Flutter):
/flutter_app/lib/main.dart
webViewController.addJavaScriptHandler(
handlerName: "FlutterApp",
callback: (args) async {
// 步骤 4: args[0] 就是 JS 发送的 message 对象
return handerWebMessage(args[0], (runJSFunctionString) {
// 步骤 6: 得到结果后,通过此回调执行JS
webViewController.evaluateJavascript(source: runJSFunctionString);
});
},
);
/flutter_app/lib/common/bridge.dart
// handerWebMessage 内部
switch (method) {
case 'chooseImage':
// 步骤 5: 分发到具体实现
result = await CommonUtils.chooseImage(params);
break;
// ... other cases
}
// ... 组装JS回调字符串并执行
原生功能执行 (Flutter -> Native):
/flutter_app/lib/common/common_utils.dart
static Future<Map<String, dynamic>> chooseImage(Map<String, dynamic> params) async {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ...);
if (image != null) {
final Uint8List bytes = await image.readAsBytes();
return {'name': image.name, 'bytes': bytes}; // 返回包含图片字节的数据
}
return {};
}
至此,一个完整的调用链路就完成了。数据从 Web UI 发出,穿过 WebView 的边界,由 Flutter 执行原生操作,再将结果安全地返回给 Web,整个过程清晰、可控。
vconsole
,可以在 App 内直接打开 Web 的控制台,查看日志、网络请求和存储。Flutter 端的逻辑则可以使用标准的 Flutter DevTools 或 IDE 的 Debugger 进行断点调试。flutter-web-interaction
项目不仅是一个功能演示,更是一种高效、灵活的混合应用开发范式。它成功地将 Flutter 作为“原生胶水层”的强大能力与 Web 生态的快速迭代能力结合在一起,为需要频繁更新 UI 同时又对原生性能有要求的应用场景,提供了极具吸引力的解决方案。
未来可探索的方向:
希望本文能为您在探索混合应用开发的道路上提供一些启发和帮助。