Flutter 与 Web 混合开发:跨平台的完美融合

Flutter 与 Web 混合开发:跨平台的完美融合

Flutter 与 Web 混合开发:跨平台的完美融合

写在前面

今天想和你聊聊一个让跨平台开发更具可能性的话题——Flutter 与 Web 混合开发。在我眼里,Flutter 就像一位多才多艺的艺术家,既能在移动平台上展现精彩,也能在 Web 世界中绽放光芒。

Flutter Web 的崛起

Flutter Web 是 Flutter 的一个重要方向,它允许我们使用同一套代码库构建运行在浏览器中的应用。随着 Flutter 3.0 的发布,Flutter Web 的性能和稳定性得到了显著提升,为混合开发开辟了新的可能。

Flutter Web 的优势

  1. 代码复用:使用同一套代码库构建移动应用和 Web 应用,减少开发和维护成本
  2. 一致的用户体验:在不同平台上提供一致的视觉和交互体验
  3. 高性能:Flutter Web 使用 CanvasKit 渲染器,提供接近原生的性能
  4. 丰富的组件库:使用 Flutter 丰富的组件库,快速构建美观的界面
  5. 热重载:支持热重载,提高开发效率

混合开发的方法

1. 嵌入式集成

将 Flutter Web 应用嵌入到现有的 Web 应用中,作为其中的一个组件或模块。

步骤 1:构建 Flutter Web 应用
# 构建 Flutter Web 应用 flutter build web 
步骤 2:嵌入到 Web 应用
<!-- 在现有 Web 应用中嵌入 Flutter Web 应用 --> <div></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> 

2. 微前端架构

使用微前端架构,将 Flutter Web 应用作为独立的微前端应用集成到主应用中。

步骤 1:创建微前端配置
// micro-frontends.config.js module.exports = { apps: [ { name: 'flutter-app', entry: 'http://localhost:3001', // Flutter Web 应用的地址 container: '#flutter-container', activeWhen: '/flutter' } ] }; 
步骤 2:集成到主应用
<!-- 主应用 index.html --> <div></div> <script src="micro-frontends.js"></script> 

3. 跨平台状态管理

使用状态管理库(如 Redux、MobX 或 Riverpod)在 Flutter 和 Web 之间共享状态。

步骤 1:创建共享状态
// 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'); } } 
步骤 2:在 Web 中访问状态
// 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; }); 

实际案例:Flutter 与 React 混合开发

1. 项目结构

project/ ├── flutter_app/ # Flutter Web 应用 ├── react_app/ # React 应用 └── shared/ # 共享代码 

2. Flutter 端实现

// 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), ), ); } } 

3. React 端实现

// 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) { // Flutter 已经加载 setupFlutterCommunication(); } else { // 监听 Flutter 加载完成 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></div> </div> ); } export default App; 

4. 集成配置

<!-- 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></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 的性能考虑

1. 选择合适的渲染器

Flutter Web 提供了两种渲染器:

  • CanvasKit:使用 WebAssembly 渲染,性能更好,但初始加载较大
  • HTML:使用 DOM 渲染,初始加载较小,但性能稍差
# 使用 CanvasKit 渲染器 flutter build web --web-renderer canvaskit # 使用 HTML 渲染器 flutter build web --web-renderer html 

2. 代码分割

使用代码分割减少初始加载时间:

// 使用 deferred loading import 'package:flutter/foundation.dart' show kIsWeb; Future<void> loadHeavyModule() async { if (kIsWeb) { await import('package:my_app/heavy_module.dart'); } } 

3. 资源优化

  • 压缩图片和资源
  • 使用适当的图片格式(WebP 或 SVG)
  • 延迟加载非关键资源

4. 网络优化

  • 使用 CDN 分发静态资源
  • 启用 HTTP/2 或 HTTP/3
  • 实现缓存策略

常见问题和解决方案

1. 跨域问题

问题: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(); }); 

2. 性能问题

问题:Flutter Web 应用在某些设备上运行缓慢。

解决方案

  • 使用 HTML 渲染器
  • 减少 Widget 树的复杂度
  • 使用 const 构造函数
  • 避免频繁的重建

3. 通信问题

问题:Flutter 与 Web 之间的通信失败。

解决方案

  • 使用统一的通信协议
  • 处理异步通信
  • 添加错误处理

4. 响应式设计

问题:Flutter Web 应用在不同屏幕尺寸上显示不正确。

解决方案

  • 使用 MediaQuery 适配不同屏幕尺寸
  • 使用 LayoutBuilder 构建响应式布局
  • 为 Web 平台添加特定的布局逻辑

最佳实践

1. 代码组织

  • 将平台特定的代码分离到不同的文件中
  • 使用抽象层处理平台差异
  • 保持共享代码的平台无关性

2. 状态管理

  • 使用跨平台的状态管理库
  • 实现状态的同步机制
  • 考虑使用本地存储或云存储持久化状态

3. 测试

  • 在不同平台上测试应用
  • 测试响应式布局
  • 测试性能和加载时间

4. 部署

  • 使用 CI/CD 自动化部署
  • 为不同平台配置不同的部署策略
  • 监控应用性能和用户体验

写在最后

Flutter 与 Web 的混合开发为我们开辟了新的可能性,让我们能够使用同一套代码库构建跨平台的应用。正如我常说的:「CSS 是流动的韵律,JS 是叙事的节奏。」而 Flutter 则是将这两者融合的交响乐。

掌握 Flutter 与 Web 混合开发的技巧,不仅能提高开发效率,还能为用户提供一致的跨平台体验。记住,像素不能偏差 1px,体验不能在任何平台上打折扣。

Read more

PX4无人机|MID360使用FAST_LIO,实现自主飞行及定点——PX4无人机配置流程(六)

PX4无人机|MID360使用FAST_LIO,实现自主飞行及定点——PX4无人机配置流程(六)

PX4固件版本为1.15.4 qgc地面站版本为4.4.5 飞控,使用微空科技MicoAir743V2 机载电脑:12代i5,ubuntu20.04 安装位置:mid360的接口对应飞机的后方 推荐阅读px4+vio实现无人机室内定位_px4+室内视觉定位-ZEEKLOG博客 和飞控连接机载电脑相关,有用 代码参考: PX4|基于FAST-LIO mid360的无人机室内自主定位及定点悬停_fastlio mid360-ZEEKLOG博客 使用视觉或动作捕捉系统进行位置估计 | PX4 指南(主) --- Using Vision or Motion Capture Systems for Position Estimation | PX4 Guide (main) 一.px4飞控设置 建议看官方文档:Using Vision or Motion

数字频率计设计在FPGA上的优化策略

FPGA上的数字频率计设计:从原理到实战的系统优化 你有没有遇到过这样的场景?手头有个信号发生器,输出一个未知频率的方波,想快速测出它的频率。用万用表?不行,普通万用表不支持高频测量。拿示波器看周期?可以,但操作繁琐、响应慢。这时候,一台高精度、响应快的 数字频率计 就显得尤为重要。 而在现代电子系统中,基于FPGA实现的数字频率计,正逐渐取代传统单片机或专用IC方案,成为高性能测量设备的核心选择。为什么?因为FPGA不仅具备并行处理能力,还能灵活重构逻辑结构,尤其适合对实时性、精度和动态范围都有严苛要求的应用。 但问题来了: 有了FPGA,是不是随便写个计数器就能搞定频率测量? 答案是否定的。 很多初学者在FPGA上做频率计时,常犯几个致命错误:直接把待测信号当钟用、跨时钟域数据没同步、低频误差大得离谱……结果要么烧板子,要么测不准,甚至系统频繁崩溃。 本文将带你深入剖析 数字频率计在FPGA平台上的完整设计链条 ,从基础原理出发,聚焦实际工程中的关键瓶颈,提出一套可落地的优化策略。我们不堆术语,只讲“人话”;不罗列理论,只谈你在调试时真正会踩的坑和对应的解法。 闸门

飞书机器人实战:5分钟搞定图片消息发送(含常见报错解决方案)

飞书机器人实战:5分钟搞定图片消息发送(含常见报错解决方案) 你是否遇到过这样的场景:服务器监控系统捕捉到一个异常峰值,你希望它能自动将一张清晰的图表截图,直接推送到团队的飞书群里,而不是一封冰冷的邮件;或者,你的自动化日报系统生成了精美的数据可视化图片,你希望它能无缝地出现在每日的晨会通知中。对于许多开发者和运维工程师来说,将图片消息集成到自动化流程中,是一个能极大提升信息传达效率和体验的“刚需”。 飞书机器人提供了强大的消息推送能力,但初次接触其图片消息发送功能时,你可能会发现它比预想的要“曲折”一些——它不像发送文本那样直接丢一个图片链接就行,而是需要经过一个“上传-获取密钥-发送”的流程。这个过程里,权限配置、tenant_access_token获取、图片上传格式、image_key的使用,每一步都可能藏着一个小坑。别担心,这篇文章就是为你准备的“避坑指南”。我们将抛开官方文档那略显冰冷的步骤罗列,从一个实战者的角度,带你用大约5分钟的时间,彻底打通从零到一发送飞书图片消息的全链路,并重点剖析那些你可能马上就会遇到的报错及其根因解决方案。我们的目标是:让你看完就能用,用了

【大模型应用篇】用 OpenClaw + 飞书打造 7x24 小时服务器运维机器人

【大模型应用篇】用 OpenClaw + 飞书打造 7x24 小时服务器运维机器人

前言 本文基于OpenClaw,也是最近超火的可在本地运行的AI Agent网关,记录从零搭建通过飞书对话管理服务器运维机器人的全过程。该机器人支持随时随地通过飞书查看服务器状态、检索日志、管理进程,其核心机制在于:由OpenClaw将聊天平台(飞书等)的消息路由至大模型,模型调用本地工具(如Shell、文件系统、浏览器)执行相应任务,最终将结果自动返回至飞书会话中,实现自动化运维交互。 架构概览 飞书 App (WebSocket 长连接)         ↕ OpenClaw Gateway (服务器上 systemd 常驻)         ↕ AI 模型 (DeepSeek v3.2/GLM 4.7)         ↕ 服务器 Shell (受白名单限制的命令执行) 核心组件: * OpenClaw Gateway:Agent 网关,管理会话、工具调用、渠道连接 * 飞书插件:通过