Flutter 与 Web 混合开发:跨平台实现方案
介绍 Flutter 与 Web 混合开发的多种方案,包括嵌入式集成、微前端架构及状态管理共享。通过 Flutter 与 React 的实际案例展示通信机制,涵盖 CanvasKit 与 HTML 渲染器选择、代码分割及资源优化等性能策略。同时解答跨域、性能瓶颈、通信失败及响应式设计等常见问题,并提供代码组织、测试与部署的最佳实践,旨在帮助开发者构建高效一致的跨平台应用体验。

介绍 Flutter 与 Web 混合开发的多种方案,包括嵌入式集成、微前端架构及状态管理共享。通过 Flutter 与 React 的实际案例展示通信机制,涵盖 CanvasKit 与 HTML 渲染器选择、代码分割及资源优化等性能策略。同时解答跨域、性能瓶颈、通信失败及响应式设计等常见问题,并提供代码组织、测试与部署的最佳实践,旨在帮助开发者构建高效一致的跨平台应用体验。

Flutter Web 是 Flutter 的一个重要方向,它允许我们使用同一套代码库构建运行在浏览器中的应用。随着 Flutter 3.0 的发布,Flutter Web 的性能和稳定性得到了显著提升,为混合开发开辟了新的可能。
将 Flutter Web 应用嵌入到现有的 Web 应用中,作为其中的一个组件或模块。
# 构建 Flutter Web 应用
flutter build web
<!-- 在现有 Web 应用中嵌入 Flutter Web 应用 -->
<div id="flutter-container"></div>
<script>
// 加载 Flutter Web 应用
window.addEventListener('load', function() {
_flutter.loader.loadEntrypoint({
serviceWorker: { serviceWorkerVersion: null },
entrypointUrl: 'flutter_app/main.dart.js',
onEntrypointLoaded: async function(engineInitializer) {
const appRunner = await engineInitializer.initializeEngine({
hostElement: document.querySelector('#flutter-container'),
});
await appRunner.runApp();
}
});
});
</script>
<script src="flutter_app/flutter.js" defer></script>
使用微前端架构,将 Flutter Web 应用作为独立的微前端应用集成到主应用中。
// micro-frontends.config.js
module.exports = {
apps: [
{
name: 'flutter-app',
entry: 'http://localhost:3001', // Flutter Web 应用的地址
container: '#flutter-container',
activeWhen: '/flutter'
}
]
};
<!-- 主应用 index.html -->
<div id="flutter-container"></div>
<script src="micro-frontends.js"></script>
使用状态管理库(如 Redux、MobX 或 Riverpod)在 Flutter 和 Web 之间共享状态。
// Flutter 中的状态管理
final counterProvider = StateProvider((ref) => 0);
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider);
return Text('Counter: $counter');
}
}
// Web 中的状态管理
import { createStore } from 'redux';
const initialState = { counter: 0 };
function counterReducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT': return { counter: state.counter + 1 };
case 'DECREMENT': return { counter: state.counter - 1 };
default: return state;
}
}
const store = createStore(counterReducer);
// 订阅状态变化
store.subscribe(() => {
document.getElementById('counter').textContent = store.getState().counter;
});
project/
├── flutter_app/ # Flutter Web 应用
├── react_app/ # React 应用
└── shared/ # 共享代码
// flutter_app/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Web App',
theme: ThemeData(primarySwatch: Colors.blue),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
// 从 Web 接收消息
final _channel = MethodChannel('flutter_web_channel');
@override
void initState() {
super.initState();
_channel.setMethodCallHandler((call) async {
if (call.method == 'incrementCounter') {
setState(() {
_counter++;
});
}
});
}
// 向 Web 发送消息
void _sendMessageToWeb() {
_channel.invokeMethod('messageFromFlutter', {'message': 'Hello from Flutter!'});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Web App')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:',),
Text('$_counter', style: Theme.of(context).textTheme.headline4,),
ElevatedButton(
onPressed: _sendMessageToWeb,
child: Text('Send Message to Web'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_counter++;
});
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
// react_app/src/App.js
import React, { useState, useEffect } from 'react';
import './App.css';
function App() {
const [counter, setCounter] = useState(0);
const [message, setMessage] = useState('');
// 初始化 Flutter
useEffect(() => {
if (window.flutterInApp) {
setupFlutterCommunication();
} else {
window.addEventListener('flutterInAppReady', setupFlutterCommunication);
}
return () => {
window.removeEventListener('flutterInAppReady', setupFlutterCommunication);
};
}, []);
// 设置与 Flutter 的通信
const setupFlutterCommunication = () => {
// 向 Flutter 发送消息
window.flutterInApp.postMessage({ method: 'incrementCounter' });
// 接收来自 Flutter 的消息
window.flutterInApp.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.message) {
setMessage(data.message);
}
});
};
// 向 Flutter 发送消息
const sendMessageToFlutter = () => {
if (window.flutterInApp) {
window.flutterInApp.postMessage({ method: 'incrementCounter' });
setCounter(counter + 1);
}
};
return (
<div className="App">
<header className="App-header">
<h1>React App</h1>
<p>Counter: {counter}</p>
<p>Message from Flutter: {message}</p>
<button onClick={sendMessageToFlutter}>Increment Counter in Flutter</button>
</header>
<div id="flutter-container"></div>
</div>
);
}
export default App;
<!-- react_app/public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="flutter-container"></div>
<!-- 嵌入 Flutter Web 应用 -->
<script>
window.flutterInApp = {
postMessage: function(message) {
// 向 Flutter 发送消息
if (window._flutter && window._flutter.loader) {
window._flutter.loader.loadEntrypoint({
serviceWorker: { serviceWorkerVersion: null },
entrypointUrl: 'flutter_app/main.dart.js',
onEntrypointLoaded: async function(engineInitializer) {
const appRunner = await engineInitializer.initializeEngine({
hostElement: document.querySelector('#flutter-container'),
});
await appRunner.runApp();
// 发送消息
window.flutterChannel.invokeMethod('incrementCounter');
}
});
}
},
addEventListener: function(event, callback) {
// 监听来自 Flutter 的消息
window.addEventListener('flutterMessage', callback);
}
};
// 通知 Flutter 准备就绪
window.dispatchEvent(new Event('flutterInAppReady'));
</script>
<script src="flutter_app/flutter.js" defer></script>
</body>
</html>
Flutter Web 提供了两种渲染器:
# 使用 CanvasKit 渲染器
flutter build web --web-renderer canvaskit
# 使用 HTML 渲染器
flutter build web --web-renderer html
使用代码分割减少初始加载时间:
// 使用 deferred loading
import 'package:flutter/foundation.dart' show kIsWeb;
Future<void> loadHeavyModule() async {
if (kIsWeb) {
await import('package:my_app/heavy_module.dart');
}
}
问题:Flutter Web 应用与主应用之间的跨域请求被阻止。
解决方案:在主应用的服务器上配置 CORS 头:
// Express 服务器配置
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
问题:Flutter Web 应用在某些设备上运行缓慢。
解决方案:
问题:Flutter 与 Web 之间的通信失败。
解决方案:
问题:Flutter Web 应用在不同屏幕尺寸上显示不正确。
解决方案:

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online