Flutter 网络请求高级技巧完全指南

引言

网络请求是移动应用开发中不可或缺的一部分。Flutter 提供了多种网络请求方案,本文将深入探讨网络请求的高级技巧,包括请求封装、错误处理、缓存策略等内容。

网络请求基础回顾

Flutter 中常用的网络请求库有:

  1. http - Flutter 官方提供的基础 HTTP 库
  2. dio - 强大的 HTTP 客户端,支持拦截器、请求取消等
  3. retrofit - 类型安全的 HTTP 客户端
// 使用 http 库
import 'package:http/http.dart' as http;

final response = await http.get(Uri.parse('https://api.example.com/data'));

高级技巧一:请求封装

创建网络服务类

import 'package:dio/dio.dart';

class ApiService {
  static final Dio _dio = Dio(BaseOptions(
    baseUrl: 'https://api.example.com',
    connectTimeout: const Duration(seconds: 10),
    receiveTimeout: const Duration(seconds: 10),
  ));

  static Future<Response<T>> get<T>(
    String path, {
    Map<String, dynamic>? queryParameters,
    Options? options,
  }) async {
    try {
      final response = await _dio.get<T>(
        path,
        queryParameters: queryParameters,
        options: options,
      );
      return response;
    } catch (e) {
      throw _handleError(e);
    }
  }

  static Future<Response<T>> post<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
  }) async {
    try {
      final response = await _dio.post<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
      );
      return response;
    } catch (e) {
      throw _handleError(e);
    }
  }

  static Exception _handleError(dynamic e) {
    if (e is DioException) {
      switch (e.type) {
        case DioExceptionType.connectionTimeout:
          return Exception('连接超时');
        case DioExceptionType.sendTimeout:
          return Exception('发送超时');
        case DioExceptionType.receiveTimeout:
          return Exception('接收超时');
        case DioExceptionType.badResponse:
          return Exception('服务器错误: ${e.response?.statusCode}');
        case DioExceptionType.cancel:
          return Exception('请求已取消');
        default:
          return Exception('网络错误: ${e.message}');
      }
    }
    return Exception('未知错误');
  }
}

高级技巧二:拦截器配置

请求拦截器

class AuthInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // 添加 token
    final token = getToken();
    if (token != null) {
      options.headers['Authorization'] = 'Bearer $token';
    }
    // 添加通用参数
    options.queryParameters['platform'] = 'flutter';
    options.queryParameters['version'] = '1.0.0';
    super.onRequest(options, handler);
  }
}

class LogInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    print('请求方法: ${options.method}');
    print('请求地址: ${options.uri}');
    print('请求参数: ${options.data}');
    super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print('响应状态: ${response.statusCode}');
    print('响应数据: ${response.data}');
    super.onResponse(response, handler);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    print('错误信息: ${err.message}');
    super.onError(err, handler);
  }
}

// 注册拦截器
_dio.interceptors.add(AuthInterceptor());
_dio.interceptors.add(LogInterceptor());

高级技巧三:请求取消

单个请求取消

CancelToken cancelToken = CancelToken();

try {
  final response = await _dio.get(
    '/api/data',
    cancelToken: cancelToken,
  );
} on DioException catch (e) {
  if (DioException.cancel == e.type) {
    print('请求已取消');
  }
}

// 取消请求
cancelToken.cancel('用户主动取消');

批量请求取消

class CancelTokenManager {
  final Map<String, CancelToken> _tokens = {};

  void addToken(String key, CancelToken token) {
    _tokens[key] = token;
  }

  void cancelToken(String key) {
    _tokens[key]?.cancel();
    _tokens.remove(key);
  }

  void cancelAll() {
    _tokens.forEach((key, token) => token.cancel());
    _tokens.clear();
  }

  CancelToken getToken(String key) {
    return _tokens[key] ?? CancelToken();
  }
}

高级技巧四:响应数据解析

泛型解析

class ApiResponse<T> {
  final int code;
  final String message;
  final T? data;

  ApiResponse({
    required this.code,
    required this.message,
    this.data,
  });

  factory ApiResponse.fromJson(Map<String, dynamic> json, T Function(dynamic) fromJson) {
    return ApiResponse<T>(
      code: json['code'] as int,
      message: json['message'] as String,
      data: json['data'] != null ? fromJson(json['data']) : null,
    );
  }
}

// 使用
final response = await ApiService.get<Map<String, dynamic>>('/api/user');
final apiResponse = ApiResponse<User>.fromJson(
  response.data!,
  (json) => User.fromJson(json as Map<String, dynamic>),
);

列表数据解析

final response = await ApiService.get<Map<String, dynamic>>('/api/users');
final apiResponse = ApiResponse<List<User>>.fromJson(
  response.data!,
  (json) => (json as List).map((e) => User.fromJson(e as Map<String, dynamic>)).toList(),
);

高级技巧五:缓存策略

内存缓存

class MemoryCache {
  static final Map<String, dynamic> _cache = {};
  static final Map<String, DateTime> _cacheTime = {};
  static const Duration _expireDuration = Duration(minutes: 5);

  static T? get<T>(String key) {
    if (!_cache.containsKey(key)) return null;
    
    final expireTime = _cacheTime[key];
    if (expireTime != null && DateTime.now().isAfter(expireTime)) {
      _cache.remove(key);
      _cacheTime.remove(key);
      return null;
    }
    
    return _cache[key] as T?;
  }

  static void set<T>(String key, T value) {
    _cache[key] = value;
    _cacheTime[key] = DateTime.now().add(_expireDuration);
  }

  static void remove(String key) {
    _cache.remove(key);
    _cacheTime.remove(key);
  }

  static void clear() {
    _cache.clear();
    _cacheTime.clear();
  }
}

网络优先缓存策略

Future<T> fetchWithCache<T>(
  String key,
  Future<T> Function() fetchFunction, {
  Duration expireDuration = const Duration(minutes: 5),
}) async {
  // 先尝试获取缓存
  final cachedData = MemoryCache.get<T>(key);
  if (cachedData != null) {
    return cachedData;
  }

  // 缓存不存在或过期,发起网络请求
  final data = await fetchFunction();
  
  // 缓存数据
  MemoryCache.set<T>(key, data);
  
  return data;
}

高级技巧六:请求重试

自动重试机制

class RetryInterceptor extends Interceptor {
  final int maxRetries;
  final List<int> retryStatusCodes;

  RetryInterceptor({
    this.maxRetries = 3,
    this.retryStatusCodes = const [500, 502, 503, 504],
  });

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    if (err.response != null && 
        retryStatusCodes.contains(err.response!.statusCode) &&
        _shouldRetry(err)) {
      _retryRequest(err, handler);
      return;
    }
    super.onError(err, handler);
  }

  bool _shouldRetry(DioException err) {
    return err.requestOptions.extra['retryCount'] != null &&
        err.requestOptions.extra['retryCount'] < maxRetries;
  }

  void _retryRequest(DioException err, ErrorInterceptorHandler handler) async {
    final currentRetry = err.requestOptions.extra['retryCount'] ?? 0;
    final nextRetry = currentRetry + 1;
    
    // 延迟重试
    await Future.delayed(Duration(seconds: nextRetry * 2));
    
    // 更新重试次数
    err.requestOptions.extra['retryCount'] = nextRetry;
    
    try {
      final response = await _dio.fetch(err.requestOptions);
      handler.resolve(response);
    } catch (e) {
      handler.reject(e as DioException);
    }
  }
}

高级技巧七:进度监听

上传进度

final response = await _dio.post(
  '/api/upload',
  data: MultipartFile.fromFileSync(
    'file_path',
    filename: 'test.png',
  ),
  onSendProgress: (int sent, int total) {
    final progress = (sent / total) * 100;
    print('上传进度: ${progress.toStringAsFixed(2)}%');
  },
);

下载进度

await _dio.download(
  'https://example.com/file.zip',
  '/path/to/save/file.zip',
  onReceiveProgress: (int received, int total) {
    final progress = (received / total) * 100;
    print('下载进度: ${progress.toStringAsFixed(2)}%');
  },
);

实战案例:完整的网络请求服务

class UserService {
  static const String _basePath = '/api/users';

  static Future<User> getUser(int id) async {
    final response = await ApiService.get<Map<String, dynamic>>('$_basePath/$id');
    return User.fromJson(response.data!);
  }

  static Future<List<User>> getUsers({int page = 1, int limit = 10}) async {
    final response = await ApiService.get<Map<String, dynamic>>(
      _basePath,
      queryParameters: {'page': page, 'limit': limit},
    );
    final data = response.data!['data'] as List;
    return data.map((e) => User.fromJson(e as Map<String, dynamic>)).toList();
  }

  static Future<User> createUser(User user) async {
    final response = await ApiService.post<Map<String, dynamic>>(
      _basePath,
      data: user.toJson(),
    );
    return User.fromJson(response.data!);
  }

  static Future<User> updateUser(int id, User user) async {
    final response = await ApiService.put<Map<String, dynamic>>(
      '$_basePath/$id',
      data: user.toJson(),
    );
    return User.fromJson(response.data!);
  }

  static Future<void> deleteUser(int id) async {
    await ApiService.delete('$_basePath/$id');
  }
}

class User {
  final int id;
  final String name;
  final String email;
  final String avatar;

  User({
    required this.id,
    required this.name,
    required this.email,
    required this.avatar,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      email: json['email'] as String,
      avatar: json['avatar'] as String,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'email': email,
      'avatar': avatar,
    };
  }
}

实战案例:网络状态管理

import 'package:flutter_bloc/flutter_bloc.dart';

abstract class UserState {}

class UserInitial extends UserState {}

class UserLoading extends UserState {}

class UserLoaded extends UserState {
  final List<User> users;

  UserLoaded({required this.users});
}

class UserError extends UserState {
  final String message;

  UserError({required this.message});
}

class UserCubit extends Cubit<UserState> {
  UserCubit() : super(UserInitial());

  Future<void> fetchUsers() async {
    emit(UserLoading());
    try {
      final users = await UserService.getUsers();
      emit(UserLoaded(users: users));
    } catch (e) {
      emit(UserError(message: e.toString()));
    }
  }
}

常见问题与解决方案

Q1:如何处理 SSL 证书问题?

A:在 Dio 中配置证书:

Dio dio = Dio();
dio.httpClientAdapter = Http2Adapter(
  ConnectionManager(
    onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
  ),
);

Q2:如何设置代理?

A:在 Dio 的 BaseOptions 中配置:

BaseOptions options = BaseOptions(
  proxy: 'http://proxy.example.com:8080',
);
Dio dio = Dio(options);

Q3:如何处理 Cookie?

A:使用 cookie_jar 包:

import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:cookie_jar/cookie_jar.dart';

final cookieJar = CookieJar();
_dio.interceptors.add(CookieManager(cookieJar));

性能优化技巧

  1. 使用连接池:复用 HTTP 连接
  2. 设置合理的超时时间:避免长时间等待
  3. 启用 gzip 压缩:减少数据传输量
  4. 缓存策略:减少重复请求
  5. 请求合并:合并多个相似请求

总结

网络请求是 Flutter 应用开发的核心功能之一。通过本文的学习,你应该能够:

  1. 创建封装良好的网络服务类
  2. 使用拦截器处理请求和响应
  3. 实现请求取消和重试机制
  4. 配置缓存策略
  5. 监听上传/下载进度

掌握这些高级技巧,能够帮助你构建更加稳定、高效的网络请求系统。

更多推荐