Dart如何在独立线程运行Stream避免阻塞UI导致掉帧
Dart 独立Isolate运行网络状态检测实现方案
原有问题代码
网络连通性检测逻辑
Future<bool> checkConnection() async { var connectivityResult = await (Connectivity().checkConnectivity()); bool connOk = false; if (connectivityResult == ConnectivityResult.mobile) { bool ii = await checkConnectionWithUrl(); if (ii == true) { connOk = true; } } else if (connectivityResult == ConnectivityResult.wifi) { bool ii = await checkConnectionWithUrl(); if (ii == true) { connOk = true; } } else if (connectivityResult == ConnectivityResult.ethernet) { bool ii = await checkConnectionWithUrl(); if (ii == true) { connOk = true; } } else if (connectivityResult == ConnectivityResult.bluetooth) { bool ii = await checkConnectionWithUrl(); if (ii == true) { connOk = true; } } else { connOk = false; } return connOk; } Future<bool> checkConnectionWithUrl() async { bool response = false; while (true) { var checkin = await client.get(Uri.https('www.google.com', '/')); if (checkin.statusCode == 200) { response = true; break; } else { sleep(const Duration(milliseconds: 500)); } } return response; }
状态轮询Stream逻辑
Stream checkStream() async* { dynamic result = false; while (true) { try { result = await checkConnection(); } catch (error) { result = false; } finally { yield result; } await Future.delayed(const Duration(seconds: 1)); } }
掉帧核心原因
- Dart的UI运行在主Isolate上,主Isolate同时负责帧绘制、手势响应、事件分发,只要主Isolate的事件循环被阻塞超过16ms(对应60fps刷新率)就会出现掉帧
- 原有代码中
checkConnectionWithUrl使用sleep()做重试等待,sleep会直接阻塞当前Isolate的事件循环,哪怕不做跨Isolate迁移,这行代码本身就会导致UI卡顿 - 网络请求异常时的无限重试逻辑会持续占用主Isolate的执行资源,挤占UI绘制的时间片
具体实现方案
Dart的多线程模型基于Isolate实现,Isolate之间内存隔离,通过消息端口(SendPort/ReceivePort)通信,常驻轮询类逻辑适合使用长驻Isolate运行,避免频繁创建销毁Isolate的性能开销。
实现前先导入依赖:
import 'dart:async'; import 'dart:isolate'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:http/http.dart' as http;
第一步:优化原有检测逻辑,移除阻塞代码
首先简化重复的分支判断,替换阻塞的sleep为非阻塞等待,增加重试上限避免无限空跑:
// 全局/静态的http客户端,避免重复创建 final http.Client _client = http.Client(); Future<bool> checkConnection() async { final connectivityResult = await Connectivity().checkConnectivity(); // 所有非无连接状态统一走实际连通性校验,去掉重复分支 if (connectivityResult != ConnectivityResult.none) { return checkConnectionWithUrl(); } return false; } Future<bool> checkConnectionWithUrl() async { const maxRetry = 3; int retryCount = 0; while (retryCount < maxRetry) { try { final response = await _client.get(Uri.https('www.google.com', '/')); if (response.statusCode == 200) { return true; } } catch (_) { // 捕获网络异常,避免直接抛出中断轮询 } retryCount++; // 替换sleep为非阻塞延迟,不阻塞事件循环 await Future.delayed(const Duration(milliseconds: 500)); } return false; }
第二步:编写子Isolate入口函数
Isolate入口函数必须是顶层函数或者类静态方法,不能是实例方法,入口函数接收主Isolate传入的SendPort用于回传消息:
// 子Isolate入口函数 void _connectivityCheckIsolateEntry(SendPort mainSendPort) { // 子Isolate自己的接收端口,用于接收主Isolate的控制指令 final isolateReceivePort = ReceivePort(); // 把子Isolate的SendPort发回主Isolate,方便主Isolate发送控制消息 mainSendPort.send(isolateReceivePort.sendPort); Timer? checkTimer; // 启动每秒一次的检测 checkTimer = Timer.periodic(const Duration(seconds: 1), (_) async { try { final isConnected = await checkConnection(); mainSendPort.send(isConnected); } catch (_) { mainSendPort.send(false); } }); // 监听主Isolate的控制指令 isolateReceivePort.listen((message) { if (message == 'stop') { checkTimer?.cancel(); isolateReceivePort.close(); Isolate.exit(); } }); }
第三步:封装服务类,管理Isolate生命周期和状态流
在主Isolate中封装服务类,负责启动/销毁子Isolate,对外暴露可直接监听的网络状态Stream:
class ConnectivityCheckService { static final ConnectivityCheckService instance = ConnectivityCheckService._(); ConnectivityCheckService._(); Isolate? _checkIsolate; SendPort? _isolateControlPort; final StreamController<bool> _statusController = StreamController<bool>.broadcast(); Stream<bool> get onStatusChange => _statusController.stream; Future<void> start() async { // 避免重复启动 if (_checkIsolate != null) return; final mainReceivePort = ReceivePort(); // 启动子Isolate _checkIsolate = await Isolate.spawn( _connectivityCheckIsolateEntry, mainReceivePort.sendPort, ); // 监听子Isolate发回的消息 mainReceivePort.listen((message) { if (message is SendPort) { // 保存子Isolate的控制端口 _isolateControlPort = message; } else if (message is bool) { // 转发网络状态到对外的Stream _statusController.add(message); } }); } void stop() { // 发送停止指令,销毁Isolate _isolateControlPort?.send('stop'); _checkIsolate?.kill(priority: Isolate.immediate); _checkIsolate = null; _isolateControlPort = null; _statusController.close(); } }
第四步:UI层使用
在应用启动时启动检测服务,需要更新网络状态的页面直接监听Stream即可,所有检测逻辑完全运行在独立Isolate,不会阻塞UI:
// 应用启动时初始化 void main() { WidgetsFlutterBinding.ensureInitialized(); ConnectivityCheckService.instance.start(); runApp(const MyApp()); } // 页面中使用 class NetworkStatusPage extends StatefulWidget { const NetworkStatusPage({super.key}); @override State<NetworkStatusPage> createState() => _NetworkStatusPageState(); } class _NetworkStatusPageState extends State<NetworkStatusPage> { bool _isConnected = false; StreamSubscription<bool>? _subscription; @override void initState() { super.initState(); _subscription = ConnectivityCheckService.instance.onStatusChange.listen((status) { setState(() { _isConnected = status; }); }); } @override void dispose() { _subscription?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Text(_isConnected ? '网络已连接' : '网络已断开'); } }
注意事项
- Isolate之间通信仅支持传递基础类型、SendPort、简单集合,不要传递闭包、自定义类实例(支持传递不可变的简单数据类,但不推荐跨Isolate传递复杂对象)
- 子Isolate中不能调用任何UI相关方法,所有UI更新必须在主Isolate收到状态后触发
- 长驻Isolate不需要频繁创建销毁,应用生命周期内启动一次即可,应用退出时可调用
stop()方法销毁 - 如果是单次耗时任务可以使用
compute函数自动创建Isolate运行,但这类常驻轮询逻辑手动管理长驻Isolate的性能远高于频繁创建销毁短生命周期Isolate
内容的提问来源于stack exchange,提问作者sanithu jayakody
相关产品推荐
相关产品推荐

