常用第三方库:dio网络库使用与封装

常用第三方库:dio网络库使用与封装

前言

dio是Flutter生态中最受欢迎的网络请求库之一,它提供了强大的功能和灵活的配置选项。本文将从实战角度深入介绍dio的使用技巧和最佳实践。

基础知识

1. dio简介

dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时等特性。

2. 基本使用

// 安装依赖
// pubspec.yaml
dependencies:
  dio: ^5.3.2

// 基本使用示例
import 'package:dio/dio.dart';

final dio = Dio();

// GET请求
Response response = await dio.get('https://api.example.com/users');

// POST请求
Response response = await dio.post(
  'https://api.example.com/users',
  data: {'name': 'test', 'age': 25},
);

3. 配置选项

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

进阶特性

1. 拦截器

拦截器是dio的一大特色,可以在请求发起前和响应返回后进行统一处理。

class CustomInterceptor extends Interceptor {
  
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // 在请求发起前统一添加token
    options.headers['Authorization'] = 'Bearer ${getToken()}';
    handler.next(options);
  }

  
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    // 统一处理响应数据
    if (response.data['code'] == 0) {
      handler.next(response);
    } else {
      handler.reject(DioException(
        requestOptions: response.requestOptions,
        response: response,
        error: response.data['message'],
      ));
    }
  }

  
  void onError(DioException err, ErrorInterceptorHandler handler) {
    // 统一错误处理
    if (err.response?.statusCode == 401) {
      // 处理token过期
      refreshToken().then((newToken) {
        // 重试请求
        dio.fetch(err.requestOptions);
      });
    }
    handler.next(err);
  }
}

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

2. 请求取消

// 创建取消令牌
final cancelToken = CancelToken();

// 发起请求
dio.get('https://api.example.com/data',
    cancelToken: cancelToken).then((response) {
  print(response.data);
}).catchError((error) {
  if (CancelToken.isCancel(error)) {
    print('请求被取消');
  }
});

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

3. 文件上传下载

// 文件上传
FormData formData = FormData.fromMap({
  'file': await MultipartFile.fromFile(
    './text.txt',
    filename: 'text.txt',
  ),
});

Response response = await dio.post(
  '/upload',
  data: formData,
  onSendProgress: (int sent, int total) {
    print('上传进度:${sent / total}');
  },
);

// 文件下载
Response response = await dio.download(
  'https://example.com/file.pdf',
  './file.pdf',
  onReceiveProgress: (int received, int total) {
    print('下载进度:${received / total}');
  },
);

实战案例:网络请求封装

1. 统一的网络请求类

class HttpClient {
  static final HttpClient _instance = HttpClient._internal();
  late Dio dio;

  factory HttpClient() => _instance;

  HttpClient._internal() {
    dio = Dio(BaseOptions(
      baseUrl: 'https://api.example.com',
      connectTimeout: Duration(seconds: 5),
      receiveTimeout: Duration(seconds: 3),
    ));
    
    // 添加拦截器
    dio.interceptors.add(CustomInterceptor());
    
    // 添加缓存拦截器
    dio.interceptors.add(CacheInterceptor());
    
    // 添加重试拦截器
    dio.interceptors.add(
      RetryInterceptor(
        dio: dio,
        retries: 3,
        retryDelays: const [
          Duration(seconds: 1),
          Duration(seconds: 2),
          Duration(seconds: 3),
        ],
      ),
    );
  }

  Future<T> get<T>(
    String path, {
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    try {
      final response = await dio.get(
        path,
        queryParameters: params,
        options: options,
        cancelToken: cancelToken,
      );
      return _handleResponse<T>(response);
    } catch (e) {
      return _handleError(e);
    }
  }

  Future<T> post<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    try {
      final response = await dio.post(
        path,
        data: data,
        queryParameters: params,
        options: options,
        cancelToken: cancelToken,
      );
      return _handleResponse<T>(response);
    } catch (e) {
      return _handleError(e);
    }
  }

  T _handleResponse<T>(Response response) {
    if (response.data['code'] == 0) {
      return response.data['data'];
    } else {
      throw ApiException(
        code: response.data['code'],
        message: response.data['message'],
      );
    }
  }

  Never _handleError(dynamic error) {
    if (error is DioException) {
      switch (error.type) {
        case DioExceptionType.connectionTimeout:
          throw const ApiException(message: '连接超时');
        case DioExceptionType.sendTimeout:
          throw const ApiException(message: '请求超时');
        case DioExceptionType.receiveTimeout:
          throw const ApiException(message: '响应超时');
        case DioExceptionType.badResponse:
          throw ApiException(
            code: error.response?.statusCode,
            message: error.response?.statusMessage,
          );
        default:
          throw ApiException(message: error.message);
      }
    }
    throw ApiException(message: error.toString());
  }
}

2. 缓存拦截器实现

class CacheInterceptor extends Interceptor {
  final _cache = <String, Response>{};

  
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // 只缓存GET请求
    if (options.method == 'GET') {
      final key = options.uri.toString();
      final cachedResponse = _cache[key];
      if (cachedResponse != null) {
        return handler.resolve(cachedResponse);
      }
    }
    handler.next(options);
  }

  
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    // 缓存响应数据
    if (response.requestOptions.method == 'GET') {
      final key = response.requestOptions.uri.toString();
      _cache[key] = response;
    }
    handler.next(response);
  }
}

3. 使用示例

// API服务类
class UserService {
  final _http = HttpClient();

  Future<User> getUserInfo(String userId) async {
    final response = await _http.get<Map<String, dynamic>>(
      '/users/$userId',
    );
    return User.fromJson(response);
  }

  Future<List<User>> getUsers({int page = 1, int size = 20}) async {
    final response = await _http.get<List<dynamic>>(
      '/users',
      params: {'page': page, 'size': size},
    );
    return response.map((json) => User.fromJson(json)).toList();
  }

  Future<void> updateUser(String userId, Map<String, dynamic> data) async {
    await _http.post<void>(
      '/users/$userId',
      data: data,
    );
  }
}

性能优化建议

  1. 合理使用拦截器

    • 避免在拦截器中进行耗时操作
    • 使用异步操作时注意处理异常
  2. 请求优化

    • 合理设置超时时间
    • 使用cancelToken取消不必要的请求
    • 避免频繁的重复请求
  3. 缓存策略

    • 针对不常变化的数据实现缓存
    • 设置合理的缓存过期时间
    • 考虑使用本地存储持久化缓存
  4. 错误处理

    • 实现统一的错误处理机制
    • 合理使用重试机制
    • 提供友好的错误提示

常见问题解决

  1. 证书验证问题
// 忽略证书验证
dio.options.validateStatus = (status) {
  return status! < 500;
};

// 或者自定义证书验证
(dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate = 
  (HttpClient client) {
    client.badCertificateCallback =
        (X509Certificate cert, String host, int port) => true;
    return client;
  };
  1. 请求取消后的内存泄漏
// 在dispose时取消所有请求
final _cancelTokens = <CancelToken>[];

void addCancelToken(CancelToken token) {
  _cancelTokens.add(token);
}


void dispose() {
  for (final token in _cancelTokens) {
    token.cancel();
  }
  _cancelTokens.clear();
  super.dispose();
}

面试题解析

1. dio与http package的区别是什么?

答:dio相比http package有以下优势:

  • 更强大的功能:支持拦截器、请求取消、文件上传下载进度监听等
  • 更好的扩展性:可以通过拦截器机制实现各种自定义功能
  • 更完善的错误处理:提供了统一的错误处理机制
  • 更方便的配置:支持全局配置和请求级配置
  • 更好的性能:支持请求队列、连接池等优化

2. 如何实现token失效自动刷新?

答:可以通过拦截器实现:

  1. 在响应拦截器中检测token失效(如401状态码)
  2. 调用刷新token的接口获取新token
  3. 使用新token重试原请求
  4. 注意处理并发请求的情况,避免多次刷新token
class TokenInterceptor extends Interceptor {
  bool _isRefreshing = false;
  List<RequestOptions> _pendingRequests = [];

  
  void onError(DioException err, ErrorInterceptorHandler handler) async {
    if (err.response?.statusCode == 401) {
      final options = err.requestOptions;
      
      if (!_isRefreshing) {
        _isRefreshing = true;
        try {
          final newToken = await refreshToken();
          // 更新所有等待的请求
          for (final request in _pendingRequests) {
            request.headers['Authorization'] = 'Bearer $newToken';
            dio.fetch(request);
          }
          _pendingRequests.clear();
        } finally {
          _isRefreshing = false;
        }
      } else {
        _pendingRequests.add(options);
      }
      return;
    }
    handler.next(err);
  }
}

3. dio如何处理大文件上传?

答:处理大文件上传需要注意以下几点:

  1. 使用FormData和MultipartFile处理文件上传
  2. 监听上传进度
  3. 实现断点续传
  4. 处理超时和错误重试
Future<void> uploadLargeFile(String filePath) async {
  final file = File(filePath);
  final fileSize = await file.length();
  
  // 创建FormData
  final formData = FormData.fromMap({
    'file': await MultipartFile.fromFile(
      filePath,
      filename: basename(filePath),
    ),
  });

  try {
    await dio.post(
      '/upload',
      data: formData,
      options: Options(
        headers: {
          Headers.contentLengthHeader: fileSize,
        },
        sendTimeout: Duration(minutes: 10),
      ),
      onSendProgress: (int sent, int total) {
        final progress = sent / total;
        print('上传进度:${(progress * 100).toStringAsFixed(2)}%');
      },
    );
  } catch (e) {
    // 处理错误,实现断点续传逻辑
  }
}

4. 如何优化dio的性能?

答:可以从以下几个方面优化dio的性能:

  1. 连接池优化

    • 合理设置maxConnectionsPerHost
    • 复用连接
  2. 请求优化

    • 使用适当的超时设置
    • 实现请求合并
    • 避免频繁创建dio实例
  3. 响应优化

    • 实现数据缓存
    • 使用压缩传输
    • 合理处理大量数据
  4. 内存优化

    • 及时释放不需要的资源
    • 避免内存泄漏
// 示例:请求合并优化
class RequestMerger {
  static final _instance = RequestMerger._();
  final _pending = <String, Completer<dynamic>>{};

  factory RequestMerger() => _instance;
  RequestMerger._();

  Future<T> request<T>(String url, Future<T> Function() fetcher) async {
    if (_pending.containsKey(url)) {
      return _pending[url]!.future as Future<T>;
    }

    final completer = Completer<T>();
    _pending[url] = completer;

    try {
      final result = await fetcher();
      completer.complete(result);
      return result;
    } catch (e) {
      completer.completeError(e);
      rethrow;
    } finally {
      _pending.remove(url);
    }
  }
}

总结

dio是一个功能强大且灵活的HTTP客户端库,通过合理的封装和优化,可以构建出一个健壮的网络请求层。本文介绍了dio的基本使用、进阶特性、实战案例和性能优化建议,希望能帮助读者更好地使用dio进行网络开发。

参考资源

  1. dio官方文档:https://pub.dev/packages/dio
  2. Flutter网络请求最佳实践:https://flutter.dev/docs/development/data-and-backend/networking
  3. dio源码:https://github.com/flutterchina/dio

如果你对文章内容有任何疑问或建议,欢迎在评论区留言交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

键盘魔术师小码哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值