跳转到正文
YGJoon 技术博客

Flutter Dio 网络请求详解

发布于:
·1.3k 字 · 约 3 分钟·作者:
Dio 落地写法:拦截器统一加 token 与错误处理、BaseOptions 全局配置、文件下载与请求取消的实战代码
Flutter Dio 网络请求详解文章封面
目录

简介

Dio 是一个强大的 Dart Http 请求库,支持拦截器、全局配置、FormData、请求取消、文件下载、超时等特性。

基本配置

安装依赖

dependencies:
  dio: ^5.3.2

创建实例

final dio = Dio(BaseOptions(
  baseUrl: 'https://api.example.com',
  connectTimeout: Duration(seconds: 5),
  receiveTimeout: Duration(seconds: 3),
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  },
));

基本请求

GET 请求

Future<Response> getUser(String id) async {
  try {
    final response = await dio.get('/users/$id');
    return response;
  } catch (e) {
    throw e;
  }
}

// 带查询参数
Future<Response> searchUsers(String query) async {
  try {
    final response = await dio.get(
      '/users',
      queryParameters: {
        'search': query,
        'page': 1,
        'per_page': 20,
      },
    );
    return response;
  } catch (e) {
    throw e;
  }
}

POST 请求

Future<Response> createUser(Map<String, dynamic> data) async {
  try {
    final response = await dio.post(
      '/users',
      data: data,
    );
    return response;
  } catch (e) {
    throw e;
  }
}

高级特性

拦截器

class AuthInterceptor extends Interceptor {
  @override
  void onRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
  ) {
    final token = getToken(); // 获取token的方法
    options.headers['Authorization'] = 'Bearer $token';
    handler.next(options);
  }
  
  @override
  void onResponse(
    Response response,
    ResponseInterceptorHandler handler,
  ) {
    // 处理响应数据
    handler.next(response);
  }
  
  @override
  void onError(
    DioException err,
    ErrorInterceptorHandler handler,
  ) {
    // 处理错误
    if (err.response?.statusCode == 401) {
      // 处理token过期
      refreshToken();
    }
    handler.next(err);
  }
}

// 添加拦截器
dio.interceptors.add(AuthInterceptor());

文件上传

Future<Response> uploadFile(File file) async {
  String fileName = file.path.split('/').last;
  FormData formData = FormData.fromMap({
    'file': await MultipartFile.fromFile(
      file.path,
      filename: fileName,
    ),
  });
  
  try {
    final response = await dio.post(
      '/upload',
      data: formData,
      onSendProgress: (sent, total) {
        print('${(sent / total * 100).toStringAsFixed(0)}%');
      },
    );
    return response;
  } catch (e) {
    throw e;
  }
}

完整示例

class ApiService {
  static final ApiService _instance = ApiService._internal();
  factory ApiService() => _instance;
  
  late final Dio _dio;
  
  ApiService._internal() {
    _dio = Dio(BaseOptions(
      baseUrl: 'https://api.example.com',
      connectTimeout: Duration(seconds: 5),
      receiveTimeout: Duration(seconds: 3),
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
    ));
    
    _dio.interceptors.add(AuthInterceptor());
    _dio.interceptors.add(LogInterceptor(
      request: true,
      requestHeader: true,
      requestBody: true,
      responseHeader: true,
      responseBody: true,
      error: true,
    ));
  }
  
  // GET 请求
  Future<T?> get<T>(
    String path, {
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    try {
      final response = await _dio.get<T>(
        path,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
      );
      return response.data;
    } on DioException catch (e) {
      _handleError(e);
      return null;
    }
  }
  
  // POST 请求
  Future<T?> post<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    try {
      final response = await _dio.post<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
      );
      return response.data;
    } on DioException catch (e) {
      _handleError(e);
      return null;
    }
  }
  
  // 错误处理
  void _handleError(DioException e) {
    switch (e.type) {
      case DioExceptionType.connectionTimeout:
        throw TimeoutException('连接超时');
      case DioExceptionType.sendTimeout:
        throw TimeoutException('请求超时');
      case DioExceptionType.receiveTimeout:
        throw TimeoutException('响应超时');
      case DioExceptionType.badResponse:
        throw HttpException(
          '服务器错误: ${e.response?.statusCode}',
        );
      case DioExceptionType.cancel:
        throw CancellationException('请求被取消');
      default:
        throw Exception('网络错误: ${e.message}');
    }
  }
}

// 使用示例
void main() async {
  final api = ApiService();
  
  try {
    // GET 请求
    final user = await api.get<Map<String, dynamic>>(
      '/users/1',
    );
    print('User: $user');
    
    // POST 请求
    final newUser = await api.post<Map<String, dynamic>>(
      '/users',
      data: {
        'name': 'John Doe',
        'email': '[email protected]',
      },
    );
    print('New user: $newUser');
  } catch (e) {
    print('Error: $e');
  }
}

最佳实践

  1. 超时给具体值:connectTimeout: 10秒、receiveTimeout: 15~30秒 是常见值;别用默认——Dio 5 的 receiveTimeout 默认无穷大,弱网下请求能 hang 到天荒地老
  2. 全局单例 + BaseOptions:baseUrl、headers、超时集中配;别每个请求 new 一个 Dio——连接池复用不上,TLS 握手每次重来
  3. 拦截器做三件事:统一加 token、401 时刷新 token 并重放等待队列(加锁防并发刷新——10 个请求同时 401,不能发 10 次刷新请求)、LogInterceptor 只在 debug 模式开(release 包打日志会泄漏 token)
  4. CancelToken 跟页面生命周期走:页面 dispose 时 cancelToken.cancel()——否则请求回来时 setState 一个已销毁的页面,直接抛异常

注意事项

  1. 接口少于十几个、只是简单 GET/POST,直接用 http 包:Dio 的拦截器/取消/FormData 用不上就是 dead dependency(实战案例的教训)
  2. DioException 按 type 处理:connectionTimeout 提示”网络不给力”,badResponse 按 statusCode 分流(401 跳登录、500 提示服务端错误)——别一律 catch 打印”请求失败”
  3. 大文件别走 response.data:上传用 FormData + onSendProgress 显示进度,下载用 dio.download 直接写文件;几十 MB 全放内存,低端机直接 OOM
  4. 自签名证书的 badCertificateCallback 只许出现在测试环境:release 包必须删掉,否则中间人攻击形同虚设

实战案例

诚实说:Dio 我配过但没在生产项目深度用过——某物流冷链项目 的 pubspec 里有 dio: ^5.7.0,但 lib/ 里实际没引用(项目初期脚手架带的,后来接口层没接上)。真正发请求的项目是 音乐播放应用,用的是最朴素的 http 包。为什么?音乐 App 的接口就是”搜歌、拿播放链接”两个 GET,http.get 两行搞定,Dio 的拦截器、取消 token、FormData 对我来说是多余重量。网络库选型看接口复杂度:十几个 REST 接口用 http 足够;要统一加 token、重试、日志,才值得上 Dio。别为了”听说 Dio 好”而引入,我那个没用上的 dio 依赖就是教训——dead dependency 也是技术债。

总结

Dio 是一个功能强大的 HTTP 客户端,通过合理使用其提供的特性,可以轻松实现各种网络请求需求。掌握 Dio 的使用对于开发高质量的 Flutter 应用至关重要。


分享这篇文章:


相关文章

上一篇
Flutter Row 和 Column 组件详解
下一篇
Flutter Container 组件详解