You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 22:48:25