如何在Flutter多页面复用单个WebSocket连接的数据?
解决方案:单例模式封装全局WebSocket服务
你可以通过单例模式封装WebSocket服务实现全局唯一连接,同时提供两种方式访问实时数据:供UI更新的Stream,以及供build外直接调用的最新数据快照,完全替代传参方案,代码可维护性和扩展性更强。
核心思路
单例模式确保整个App只有一个WebSocket连接实例,所有页面共享同一连接和数据。同时在单例中封装连接管理、消息监听、状态维护逻辑,避免重复代码。
具体实现步骤
1. 封装WebSocket单例服务
创建一个全局单例类,处理连接、消息接收、状态管理:
import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'dart:async'; import 'dart:developer' as dev; class WebSocketService { // 单例实例(全局唯一) static final WebSocketService _instance = WebSocketService._internal(); factory WebSocketService() => _instance; WebSocketService._internal(); late WebSocketChannel _channel; // 保存最新消息快照,供build外直接访问 String? latestMessage; // 对外暴露的消息流,供StreamBuilder实时更新UI late Stream<String> messageStream; // 连接状态标记 bool isConnected = false; // 初始化WebSocket连接 void connect() { if (isConnected) return; // 避免重复连接 WebSocket.connect("wss://MY_DOMAIN_URL", headers: { 'Connection': 'Upgrade', 'Upgrade': 'WebSocket', }).then((ws) { dev.log('SOCKET CONNECTED'); _channel = IOWebSocketChannel(ws); isConnected = true; // 绑定消息流,同时更新最新消息快照 messageStream = _channel.stream.map((message) { latestMessage = message.toString(); dev.log('Received message: $latestMessage'); return latestMessage!; }); // 监听连接关闭事件 ws.done.then((_) { dev.log('SOCKET DISCONNECTED'); isConnected = false; // 可选:添加自动重连逻辑 // Future.delayed(const Duration(seconds: 3), () => connect()); }); }).catchError((error) { dev.log('Connection error: $error'); isConnected = false; }); } // 发送消息(如果业务需要) void sendMessage(String message) { if (isConnected) { _channel.sink.add(message); } else { dev.log('Cannot send message: not connected'); } } // 主动关闭连接 void disconnect() { if (isConnected) { _channel.sink.close(); isConnected = false; } } }
2. 全局初始化连接
在App启动时初始化连接,确保所有页面加载前已建立连接:
void main() { // 初始化WebSocket连接 WebSocketService().connect(); runApp(const MyApp()); }
3. 页面中使用数据
方式一:用StreamBuilder实时更新UI
适合需要根据消息自动刷新UI的场景:
class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: StreamBuilder<String>( stream: WebSocketService().messageStream, initialData: WebSocketService().latestMessage, // 初始化显示最新快照 builder: (context, snapshot) { if (snapshot.hasData) { return Center(child: Text('最新消息:${snapshot.data}')); } else if (snapshot.hasError) { return Center(child: Text('连接错误:${snapshot.error}')); } return const Center(child: CircularProgressIndicator()); }, ), ); } }
方式二:在build外直接访问最新数据
适合业务逻辑处理(比如按钮点击、后台任务):
class SettingsPage extends StatefulWidget { const SettingsPage({super.key}); @override State<SettingsPage> createState() => _SettingsPageState(); } class _SettingsPageState extends State<SettingsPage> { late WebSocketService _wsService; @override void initState() { super.initState(); _wsService = WebSocketService(); // 可选:监听消息变化,处理业务逻辑 _wsService.messageStream.listen((message) { dev.log('设置页收到消息:$message'); // 这里可以执行非UI的业务逻辑,比如存储本地、调用API等 }); } void handleCheckLatestMessage() { // 直接获取最新消息快照 String? latestMsg = _wsService.latestMessage; if (latestMsg != null) { dev.log('当前最新消息:$latestMsg'); // 执行业务操作 } } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ElevatedButton( onPressed: handleCheckLatestMessage, child: const Text('获取最新消息'), ), ), ); } }
方案优势对比
- 比起全局变量:单例封装了连接逻辑、状态管理,避免全局变量的混乱,便于统一处理重连、错误等事件
- 比起Navigator传参:无需在页面间层层传递数据,所有页面直接调用单例即可,扩展性更强
- 同时支持UI实时更新和build外业务逻辑访问,覆盖所有使用场景
注意事项
- 可以添加自动重连逻辑(代码中已注释),提升稳定性
- 用户退出登录等场景下,记得主动调用
disconnect()关闭连接并清空latestMessage - 如果项目已使用Provider/Riverpod等状态管理库,也可以将单例集成到状态管理中,进一步优化状态共享
内容的提问来源于stack exchange,提问作者Kaushik Makwana
相关产品推荐
相关产品推荐

