Flutter 异步编程与网络通信

Flutter 网络通信实战:Dio 深度封装、REST/GraphQL/WebSocket 集成、Isolate 并行计算、离线优先与文件传输。

开篇:异步是 Flutter 的 DNA

Dart 语言是单线程事件循环模型,与 JavaScript 类似。这种设计避免了多线程的竞态条件问题,但也意味着所有代码默认在主 isolate 上执行——如果某个操作阻塞了事件循环,整个 UI 都会卡住。因此,在 Flutter 中编写高效的异步代码不仅是性能优化,更是用户体验的基础。

从 Future 的链式调用到 async/await 的语法糖,从 Stream 的响应式数据流到 Isolate 的真正并行计算,Dart 提供了丰富的异步工具。而在网络通信层面,从简单的 http 调用到 Dio 的强大封装,从 RESTful API 到 GraphQL 查询,从 WebSocket 实时通信到离线优先的本地缓存策略——Flutter 的网络生态足够成熟,可以支撑从小型应用到大型平台的各种需求。


一、Dart 异步模型深度解析

1.1 事件循环(Event Loop)

// Dart 单线程事件循环的核心流程:
// 1. 执行 main() 中的同步代码
// 2. 检查 Microtask 队列,全部执行
// 3. 检查 Event 队列,执行一个事件
// 4. 重复 2-3

void main() {
  print('1 - 同步代码');
  
  Future.microtask(() => print('2 - Microtask'));
  
  Future(() => print('3 - Event (Future)'));
  
  Future.delayed(Duration.zero, () => print('4 - Delayed Future'));
  
  print('5 - 同步代码结束');
}

// 输出顺序:1 → 5 → 2 → 3 → 4

优先级:同步代码 > Microtask > Event。理解这个顺序对预测代码执行时序至关重要。

一句话总结:Dart 事件循环的同步→Microtask→Event 优先级规则,是理解和调试异步行为的基础。

1.2 async/await 语法糖

// 原始 Future 链式调用
Future<String> fetchData() {
  return http.get(Uri.parse('/api/data'))
    .then((response) {
      if (response.statusCode == 200) return response.body;
      throw Exception('请求失败');
    })
    .then((body) => jsonDecode(body))
    .then((data) => data['title'] as String)
    .catchError((error) => '默认值');
}

// async/await 改写(更易读)
Future<String> fetchData() async {
  try {
    final response = await http.get(Uri.parse('/api/data'));
    if (response.statusCode != 200) throw Exception('请求失败');
    final data = jsonDecode(response.body);
    return data['title'] as String;
  } catch (e) {
    return '默认值';
  }
}

// 并行等待多个 Future
Future<Map<String, dynamic>> fetchDashboard() async {
  final results = await Future.wait([
    fetchUserProfile(),
    fetchNotifications(),
    fetchRecentOrders(),
  ]);
  
  return {
    'profile': results[0],
    'notifications': results[1],
    'orders': results[2],
  };
}

一句话总结:async/await 不是新机制,而是 Future 的语法糖,它让异步代码的阅读和编写像同步代码一样自然。

1.3 Stream 响应式编程

import 'dart:async';

// Stream 控制器
class ChatService {
  final _messageController = StreamController<Message>.broadcast();
  Stream<Message> get messages => _messageController.stream;
  
  void sendMessage(Message msg) {
    _messageController.add(msg);
  }
  
  void dispose() {
    _messageController.close();
  }
}

// Stream 变换
void demoStream() async {
  final stream = Stream.fromIterable([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
  
  await stream
    .map((n) => n * 2)
    .where((n) => n > 5)
    .take(3)
    .forEach(print);  // 12, 14, 16
  
  // 监听 WebSocket 消息
  final wsStream = webSocketChannel.stream;
  
  wsStream
    .map((data) => jsonDecode(data))
    .where((json) => json['type'] == 'message')
    .cast<Map<String, dynamic>>()
    .map((json) => Message.fromJson(json))
    .listen(
      (message) => displayMessage(message),
      onError: (e) => logError(e),
      onDone: () => showDisconnected(),
    );
}

// 自定义 Stream(async* / yield)
Stream<int> countdown(int from) async* {
  for (var i = from; i >= 0; i--) {
    await Future.delayed(Duration(seconds: 1));
    yield i;  // 产生一个值
  }
  yield -1;  // 完成信号
}

// yield* 委托给另一个 Stream
Stream<int> doubleCountdown(int from) async* {
  yield* countdown(from).map((n) => n * 2);
}

一句话总结:Stream 是 Dart 对"异步数据序列"的抽象,通过 map/where/take 等操作符可以像处理集合一样处理异步事件流。


二、Isolate:真正的并行计算

2.1 为什么要用 Isolate?

// ❌ 在主 isolate 执行耗时计算 → UI 卡顿
void heavyComputation() {
  var sum = 0;
  for (var i = 0; i < 100000000; i++) {
    sum += i;
  }
  return sum;
}

// ✅ 使用 compute() 在后台 isolate 执行
Future<int> heavyComputationAsync() async {
  return compute((message) {
    var sum = 0;
    for (var i = 0; i < 100000000; i++) {
      sum += i;
    }
    return sum;
  }, null);
}

// compute() 自动处理 isolate 创建、通信、销毁
// 适用场景:图片处理、JSON 解析、大文件处理、复杂计算

2.2 手动管理 Isolate

import 'dart:isolate';

class ImageProcessor {
  late SendPort _sendPort;
  late Isolate _isolate;
  
  Future<void> init() async {
    final receivePort = ReceivePort();
    _isolate = await Isolate.spawn(
      _imageProcessorEntry,
      receivePort.sendPort,
    );
    _sendPort = await receivePort.first;
  }
  
  Future<Uint8List> processImage(Uint8List imageData) async {
    final receivePort = ReceivePort();
    _sendPort.send({
      'replyPort': receivePort.sendPort,
      'image': imageData,
    });
    return await receivePort.first;
  }
  
  void dispose() {
    _isolate.kill();
  }
  
  static void _imageProcessorEntry(SendPort mainSendPort) {
    final receivePort = ReceivePort();
    mainSendPort.send(receivePort.sendPort);
    
    receivePort.listen((message) async {
      final replyPort = message['replyPort'] as SendPort;
      final image = message['image'] as Uint8List;
      
      // 耗时图像处理
      final processed = await _applyFilters(image);
      replyPort.send(processed);
    });
  }
  
  static Future<Uint8List> _applyFilters(Uint8List image) async {
    // 图像处理逻辑
    return image;
  }
}

一句话总结:Isolate 让 Dart 突破了单线程限制,但通信开销较大——compute() 适合一次性任务,手动管理适合需要长期保持的后台工作线程。


三、Dio 深度实战

Dio 是 Flutter 生态中最强大的 HTTP 客户端,提供了拦截器、全局配置、请求取消、进度监听等高级功能。

3.1 基础封装

import 'package:dio/dio.dart';

class ApiClient {
  late final Dio _dio;
  
  ApiClient({String baseUrl = 'https://api.example.com'}) {
    _dio = Dio(BaseOptions(
      baseUrl: baseUrl,
      connectTimeout: Duration(seconds: 10),
      receiveTimeout: Duration(seconds: 30),
      headers: {'Content-Type': 'application/json'},
    ));
    
    _setupInterceptors();
  }
  
  void _setupInterceptors() {
    // 请求拦截器:添加 Token
    _dio.interceptors.add(InterceptorsWrapper(
      onRequest: (options, handler) async {
        final token = await _getToken();
        if (token != null) {
          options.headers['Authorization'] = 'Bearer $token';
        }
        print('→ REQUEST: ${options.method} ${options.path}');
        handler.next(options);
      },
      onResponse: (response, handler) {
        print('← RESPONSE [${response.statusCode}]: ${response.requestOptions.path}');
        handler.next(response);
      },
      onError: (error, handler) async {
        if (error.response?.statusCode == 401) {
          // Token 过期,尝试刷新
          final refreshed = await _refreshToken();
          if (refreshed) {
            // 重试原请求
            final retry = await _dio.fetch(error.requestOptions);
            handler.resolve(retry);
            return;
          }
        }
        handler.next(error);
      },
    ));
    
    // 日志拦截器(仅开发环境)
    if (kDebugMode) {
      _dio.interceptors.add(LogInterceptor(
        requestBody: true,
        responseBody: true,
      ));
    }
  }
  
  Future<T> get<T>(String path, {
    Map<String, dynamic>? queryParameters,
    Options? options,
  }) async {
    final response = await _dio.get<T>(path,
      queryParameters: queryParameters,
      options: options,
    );
    return response.data as T;
  }
  
  Future<T> post<T>(String path, {dynamic data}) async {
    final response = await _dio.post<T>(path, data: data);
    return response.data as T;
  }
  
  // 文件下载
  Future<void> downloadFile(
    String url,
    String savePath, {
    void Function(int received, int total)? onProgress,
    CancelToken? cancelToken,
  }) async {
    await _dio.download(
      url,
      savePath,
      onReceiveProgress: onProgress,
      cancelToken: cancelToken,
    );
  }
}

3.2 完整 Repository 层

// 统一响应封装
class ApiResponse<T> {
  final bool success;
  final T? data;
  final String? message;
  final int? code;
  
  ApiResponse({required this.success, this.data, this.message, this.code});
  
  factory ApiResponse.fromJson(
    Map<String, dynamic> json,
    T Function(dynamic) converter,
  ) {
    return ApiResponse(
      success: json['success'] ?? false,
      data: json['data'] != null ? converter(json['data']) : null,
      message: json['message'],
      code: json['code'],
    );
  }
}

// Repository 实现
class UserRepository {
  final ApiClient _client;
  UserRepository(this._client);
  
  Future<User> getUser(String id) async {
    final response = await _client.get<Map<String, dynamic>>('/users/$id');
    final apiResponse = ApiResponse.fromJson(response!, (d) => User.fromJson(d));
    if (!apiResponse.success) throw ApiException(apiResponse.message);
    return apiResponse.data!;
  }
  
  Future<List<User>> searchUsers(String query) async {
    final response = await _client.get<Map<String, dynamic>>(
      '/users',
      queryParameters: {'q': query},
    );
    final apiResponse = ApiResponse.fromJson(
      response!,
      (d) => (d as List).map((u) => User.fromJson(u)).toList(),
    );
    return apiResponse.data ?? [];
  }
  
  Future<User> updateProfile(String userId, UpdateProfileRequest request) async {
    final response = await _client.post<Map<String, dynamic>>(
      '/users/$userId',
      data: request.toJson(),
    );
    return User.fromJson(ApiResponse.fromJson(response!, (d) => d).data!);
  }
}

3.3 请求取消

class SearchController extends ChangeNotifier {
  final _api = ApiClient();
  CancelToken? _cancelToken;
  List<SearchResult> _results = [];
  
  List<SearchResult> get results => _results;
  
  Future<void> search(String query) async {
    // 取消上一个请求
    _cancelToken?.cancel('新搜索请求');
    _cancelToken = CancelToken();
    
    try {
      final response = await _api.get('/search',
        queryParameters: {'q': query},
        cancelToken: _cancelToken,
      );
      _results = (response as List).map((r) => SearchResult.fromJson(r)).toList();
      notifyListeners();
    } on DioException catch (e) {
      if (CancelToken.isCancel(e)) return;  // 正常取消,忽略
      rethrow;
    }
  }
  
  @override
  void dispose() {
    _cancelToken?.cancel('Controller disposed');
    super.dispose();
  }
}

一句话总结:Dio 的拦截器架构将认证、日志、重试等横切关注点从业务代码中剥离,配合 cancelToken 实现防抖搜索,是 Flutter 网络层的工业标准。


四、REST API 最佳实践

4.1 数据模型层

import 'package:json_annotation/json_annotation.dart';

part 'models.g.dart';  // 由 build_runner 生成

@JsonSerializable()
class User {
  final String id;
  final String name;
  final String? email;
  @JsonKey(name: 'created_at')
  final DateTime createdAt;
  @JsonKey(defaultValue: [])
  final List<String> roles;
  
  const User({
    required this.id,
    required this.name,
    this.email,
    required this.createdAt,
    required this.roles,
  });
  
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
  
  User copyWith({String? name, String? email, List<String>? roles}) {
    return User(
      id: id,
      name: name ?? this.name,
      email: email ?? this.email,
      createdAt: createdAt,
      roles: roles ?? this.roles,
    );
  }
}

@JsonSerializable()
class ApiError {
  final String code;
  final String message;
  final Map<String, dynamic>? details;
  
  ApiError({required this.code, required this.message, this.details});
  
  factory ApiError.fromJson(Map<String, dynamic> json) => _$ApiErrorFromJson(json);
  
  @override
  String toString() => 'ApiError($code): $message';
}

4.2 统一错误处理

class ApiException implements Exception {
  final String message;
  final int? statusCode;
  final ApiError? error;
  
  ApiException(this.message, {this.statusCode, this.error});
  
  factory ApiException.fromDio(DioException e) {
    switch (e.type) {
      case DioExceptionType.connectionTimeout:
      case DioExceptionType.sendTimeout:
      case DioExceptionType.receiveTimeout:
        return ApiException('请求超时,请检查网络', statusCode: 408);
      case DioExceptionType.connectionError:
        return ApiException('网络连接失败', statusCode: 0);
      case DioExceptionType.badResponse:
        final status = e.response?.statusCode;
        final data = e.response?.data;
        if (data != null && data is Map<String, dynamic>) {
          return ApiException(
            data['message'] ?? '请求失败',
            statusCode: status,
            error: ApiError.fromJson(data),
          );
        }
        return ApiException('请求失败 (HTTP $status)', statusCode: status);
      default:
        return ApiException('未知错误: ${e.message}', statusCode: 0);
    }
  }
  
  bool get isNetworkError => statusCode == 0 || statusCode == 408;
  bool get isAuthError => statusCode == 401 || statusCode == 403;
}

// 全局错误处理 Widget
class AsyncValueWidget<T> extends StatelessWidget {
  final AsyncValue<T> value;
  final Widget Function(T data) data;
  final Widget? loading;
  final Widget Function(Object error, StackTrace? stack)? error;
  
  const AsyncValueWidget({
    super.key,
    required this.value,
    required this.data,
    this.loading,
    this.error,
  });
  
  @override
  Widget build(BuildContext context) {
    return value.when(
      data: data,
      loading: () => loading ?? const Center(child: CircularProgressIndicator()),
      error: (e, st) => error?.call(e, st) ?? _buildError(context, e),
    );
  }
  
  Widget _buildError(BuildContext context, Object error) {
    final message = error is ApiException
        ? error.message
        : '发生未知错误';
    return Center(child: Text(message, style: TextStyle(color: Colors.red)));
  }
}

一句话总结:清晰的数据模型层 + 统一的错误处理机制,是构建可维护网络层的基石。


五、WebSocket 实时通信

import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/io.dart';

class ChatWebSocketService {
  WebSocketChannel? _channel;
  final _messageController = StreamController<ChatMessage>.broadcast();
  final _statusController = StreamController<ConnectionStatus>.broadcast();
  
  Stream<ChatMessage> get messages => _messageController.stream;
  Stream<ConnectionStatus> get status => _statusController.stream;
  
  void connect(String roomId, String token) {
    _disconnect();
    _statusController.add(ConnectionStatus.connecting);
    
    try {
      _channel = IOWebSocketChannel.connect(
        'wss://chat.example.com/rooms/$roomId',
        headers: {'Authorization': 'Bearer $token'},
      );
      
      _channel!.stream.listen(
        (data) {
          _statusController.add(ConnectionStatus.connected);
          final json = jsonDecode(data);
          _messageController.add(ChatMessage.fromJson(json));
        },
        onError: (error) {
          _statusController.add(ConnectionStatus.error);
          _scheduleReconnect(roomId, token);
        },
        onDone: () {
          _statusController.add(ConnectionStatus.disconnected);
          _scheduleReconnect(roomId, token);
        },
      );
      
      // 启动心跳
      _startHeartbeat();
    } catch (e) {
      _statusController.add(ConnectionStatus.error);
      _scheduleReconnect(roomId, token);
    }
  }
  
  void sendMessage(String content) {
    _channel?.sink.add(jsonEncode({
      'type': 'message',
      'content': content,
      'timestamp': DateTime.now().toIso8601String(),
    }));
  }
  
  void _startHeartbeat() {
    Timer.periodic(Duration(seconds: 30), (_) {
      _channel?.sink.add(jsonEncode({'type': 'ping'}));
    });
  }
  
  void _scheduleReconnect(String roomId, String token) {
    Future.delayed(Duration(seconds: 5), () => connect(roomId, token));
  }
  
  void _disconnect() {
    _channel?.sink.close();
    _channel = null;
  }
  
  void dispose() {
    _disconnect();
    _messageController.close();
    _statusController.close();
  }
}

enum ConnectionStatus { connecting, connected, disconnected, error }

一句话总结:WebSocket 连接管理需要处理连接、心跳、重连、断开等全生命周期,封装为服务类是最佳实践。


六、离线优先架构

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:hive/hive.dart';

class OfflineFirstRepository<T> {
  final ApiClient _api;
  final Box<T> _localBox;
  final String apiEndpoint;
  
  OfflineFirstRepository(this._api, this._localBox, this.apiEndpoint);
  
  Future<List<T>> getAll() async {
    final connectivity = await Connectivity().checkConnectivity();
    
    if (connectivity == ConnectivityResult.none) {
      // 离线:返回本地缓存
      return _localBox.values.toList();
    }
    
    try {
      // 在线:获取远程数据并缓存
      final remoteData = await _api.get<List<dynamic>>(apiEndpoint);
      final items = remoteData!.map((d) => _deserialize(d)).toList();
      
      // 更新本地缓存
      await _localBox.clear();
      for (var i = 0; i < items.length; i++) {
        await _localBox.put(i, items[i]);
      }
      
      return items;
    } catch (e) {
      // 请求失败次选本地数据
      return _localBox.values.toList();
    }
  }
  
  Future<T> create(T item) async {
    // 先生成本地 ID
    final localId = DateTime.now().millisecondsSinceEpoch.toString();
    await _localBox.put(localId, item);
    
    // 尝试同步到服务器
    _syncToServer();
    
    return item;
  }
  
  Future<void> _syncToServer() async {
    final pending = _localBox.values.toList();
    final connectivity = await Connectivity().checkConnectivity();
    
    if (connectivity == ConnectivityResult.none) return;
    
    for (final item in pending) {
      try {
        await _api.post(apiEndpoint, data: _serialize(item));
        // 标记同步成功
      } catch (e) {
        // 保留在本地,下次重试
        break;
      }
    }
  }
  
  T _deserialize(dynamic data) => throw UnimplementedError();
  Map<String, dynamic> _serialize(T item) => throw UnimplementedError();
}

一句话总结:离线优先不是不依赖网络,而是在网络不可用时 gracefully 降级到本地缓存,确保核心功能始终可用。


七、文件上传与下载

// 多文件上传
Future<void> uploadFiles(List<XFile> files) async {
  final formData = FormData();
  
  for (final file in files) {
    formData.files.add(MapEntry(
      'attachments',
      await MultipartFile.fromFile(file.path, filename: file.name),
    ));
  }
  
  final response = await dio.post('/upload',
    data: formData,
    onSendProgress: (sent, total) {
      final progress = sent / total;
      print('上传进度: ${(progress * 100).toStringAsFixed(1)}%');
    },
  );
}

// 断点续传下载
Future<void> downloadWithResume(String url, String savePath) async {
  final file = File(savePath);
  var startByte = 0;
  
  if (await file.exists()) {
    startByte = await file.length();
  }
  
  final cancelToken = CancelToken();
  
  await dio.download(
    url,
    savePath,
    cancelToken: cancelToken,
    options: Options(headers: {'Range': 'bytes=$startByte-'}),
    onReceiveProgress: (received, total) {
      if (total != -1) {
        print('下载进度: ${((received + startByte) / (total + startByte) * 100).toStringAsFixed(1)}%');
      }
    },
  );
}

FAQ

Q1: Future.wait 中一个失败会怎样?

默认情况下,任何一个 Future 失败都会导致整个 wait 失败。使用 Future.wait(futures, eagerError: true) 可尽早失败,或使用 Result 类型包装每个 Future。

Q2: Stream 的 listen 可以多次调用吗?

取决于 Stream 类型:

  • 单订阅 Stream(默认)只能 listen 一次
  • 广播 Stream(.asBroadcastStream()StreamController.broadcast())可多次 listen

Q3: 如何避免内存泄漏?

始终取消 Stream 订阅和关闭控制器:

final subscription = stream.listen(...);
// 在 dispose 中:
subscription.cancel();
controller.close();

Q4: Dio 的拦截器执行顺序?

请求:按添加顺序执行(FIFO)
响应:按添加顺序的逆序执行(LIFO)
错误:按添加顺序的逆序执行

Q5: compute() 的参数有什么限制?

传递给 compute 的函数和参数必须是顶级函数或静态方法,且参数必须是可序列化的——不能包含 BuildContext、StreamController 等不可传递的对象。

Q6: WebSocket 和 SSE 怎么选?

  • 双向实时通信(聊天、游戏、协同编辑)→ WebSocket
  • 服务器单向推送(股票行情、通知、日志流)→ SSE(更轻量,自动重连,兼容 HTTP 缓存)

Q7: 如何处理网络请求缓存?

Dio 内置缓存拦截器:dio.interceptors.add(DioCacheInterceptor(options: cacheOptions)),配合 CachePolicy.request/CachePolicy.refresh 灵活控制缓存策略。


相关阅读

  • https://plumephp.com/flutter-local-storage/ — 本地存储与持久化
  • https://plumephp.com/graphql-fundamentals/ — GraphQL 基础
  • https://plumephp.com/dart-basics/ — Dart 异步编程语言特性

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「Flutter」更多文章

  1. Widget 体系与布局系统
  2. Flutter 状态管理全解析
  3. Flutter 测试策略与自动化