Dio
dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等...
添加依赖
dependencies:
dio: ^4.0.6
网络请求
网络请求方法
Get请求
请求指定的页面信息,并返回实体主体
static Future doGet() async {
String url = 'https://www.xxx.com/api/index/getInfo';
Response response;
Dio dio = Dio();
response = await dio.get('$url?device=android&idcode=123456');
print(response.data.toString());
// 请求参数也可以通过对象传递,上面的代码等同于
// response = await dio
// .get(url, queryParameters: {'device': 'android', "idcode": "123456"});
// print(response.data.toString());
}
Post请求
向指定资源提交数据进行处理请求(例如提交表单或者上传文件)
static Future doPost() async {
Response response;
Dio dio = Dio();
// 表单提交操作
// FormData formData = FormData.fromMap({
// 'device': 'android',
// 'idcode': '123456',
// 'type': '1'
// });
response = await dio.post(
'https://www.xxx.com/api/house/dialogRemind',
data: {'device': 'android', "idcode": "123456", 'type': '1'});
print(response.data.toString());
}
Put请求
从客户端向服务器传送的数据取代指定的文档内容
static Future doPut(
String path, {
data,
Map<String, dynamic> params,
Options options,
CancelToken cancelToken,
}) async {
Response response;
Dio dio = Dio();
Options requestOptions = options ?? Options();
response = await dio.put(path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken);
print(response.data.toString());
}
Patch请求
与PUT类似,发送一个修改数据的请求,区别在于PATCH代表部分更新
static Future doPatch(
String path, {
data,
Map<String, dynamic> params,
Options options,
CancelToken cancelToken,
}) async {
Response response;
Dio dio = Dio();
Options requestOptions = options ?? Options();
response = await dio.patch(path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken);
return response.data;
}
Delete请求
请求服务器删除指定内容
static Future doDelete(
String path, {
data,
Map<String, dynamic> params,
Options options,
CancelToken cancelToken,
}) async {
Response response;
Dio dio = Dio();
Options requestOptions = options ?? Options();
response = await dio.delete(path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken);
return response.data;
}
网络请求配置
并发请求
// 发起多个并发请求
static Future complicating() async {
Dio dio = Dio();
List<Response> list = await Future.wait([
dio.get(
'https://www.xxx.com/api/index/getInfo?device=android&idcode=123456'),
dio.post('https://www.xxx.com/api/house/dialogRemind',
data: {'device': 'android', "idcode": "123456", 'type': '1'})
]);
for (Response response in list) {
// 结果依次返回
print(response.data.toString());
}
}
使用代理
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
client.findProxy = (uri) {
return 'PROXY ${Config.proxyIp}:${Config.proxyPort}';
};
//代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
};
添加cookie管理
# 添加依赖
path_provider: ^2.0.9
dio_cookie_manager: ^2.0.0
/// 加载cookie
_addCookie() async {
Directory appDocDir = await getApplicationDocumentsDirectory();
PersistCookieJar _cookieJar =
PersistCookieJar(storage: FileStorage(appDocDir.path + '/.cookies/'));
_dio.interceptors.add(CookieManager(_cookieJar));
}
添加日志拦截器
// 添加日志输出拦截器
_dio.interceptors
.add(LogInterceptor(requestBody: true, responseBody: true));
// 添加错误处理拦截器(自定义ErrorInterceptor类)
_dio.interceptors.add(ErrorInterceptor());
添加缓存拦截器
// 自定义NetCacheInterceptor缓存拦截器
dio.interceptors.add(NetCacheInterceptor());
网络封装
BaseParam类
统一携带固定的请求参数,域名debug自动切换
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_common/config/config.dart';
import 'package:flutter_common/config/sp_key.dart';
import 'package:flutter_common/config/url.dart';
import 'package:flutter_common/res/string.dart';
import 'package:flutter_common/util/device_info_util.dart';
import 'package:flutter_common/util/package_info_util.dart';
import 'package:flutter_common/util/sp_util.dart';
/// 基础参数
class BaseParam {
/// 获取参数(用于自动带上必传参数)
static Map<String, dynamic> getBaseMap() {
String device = Config.unknown;
if (Platform.isAndroid) {
device = Config.android;
} else if (Platform.isIOS || Platform.isMacOS) {
device = Config.ios;
} else if (kIsWeb) {
device = Config.web;
}
/**
* device:区别端
*/
Map<String, dynamic> map = {
'device': device,
'idcode': DeviceInfoUtil().appDeviceId,
'app_id': DeviceInfoUtil().appDeviceId,
'version': PackageInfoUtil().versionName,
'project': 'flutter'
};
// 语言版本
String language = SPUtil()
.getString(SpKey.appLanguage, defaultValue: StringModel.languageCN);
if (language == StringModel.languageEN) {
map['lang'] = 'en-us';
}
return map;
}
/// 构建URL(用于环境切换)
static String buildUrl(url) {
if (Config.isDebug) {
// 切换成调试模式
bool isDebug =
SPUtil().getBoolean(SpKey.appDebugModel, defaultValue: false);
if (isDebug) {
// 切换成调试模式
if (url.contains(Url.hostLumen)) {
url = url.replaceFirst(Url.hostLumen, Url.hostLumenDebug);
} else if (url.contains(Url.hostService)) {
url = url.replaceFirst(Url.hostService, Url.hostServiceDebug);
} else if (url.contains(Url.hostNews)) {
url = url.replaceFirst(Url.hostNews, Url.hostNewsDebug);
} else if (url.contains(Url.hostHYPerf)) {
url = url.replaceFirst(Url.hostHYPerf, Url.hostHYPerfDebug);
} else if (url.contains(Url.hostApi)) {
url = url.replaceFirst(Url.hostApi, Url.hostApiDebug);
} else {
url = url.replaceFirst(Url.host, Url.hostDebug);
}
}
}
return url;
}
}
HttpRequest类
执行http请求,主要支持get和post请求(日志拦截器/错误信息处理/cookie管理/代理抓包/网络缓存)
// ignore_for_file: empty_catches
import 'dart:io';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:flutter_common/config/config.dart';
import 'package:flutter_common/config/http_key.dart';
import 'package:flutter_common/http/base/base_param.dart';
import 'package:flutter_common/http/interceptor/error_interceptor.dart';
import 'package:flutter_common/http/interceptor/net_cache_interceptor.dart';
import 'package:path_provider/path_provider.dart';
/// 网络请求
class HttpRequest {
late Dio _dio;
get dio => _dio;
final CancelToken _cancelToken = CancelToken();
factory HttpRequest() => _instance;
static final HttpRequest _instance = HttpRequest._init();
HttpRequest._init() {
// BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
BaseOptions options = BaseOptions(
headers: {'Content-Type': Headers.jsonContentType},
// 连接服务器超时时间,单位是毫秒.
connectTimeout: 30 * 1000,
// 响应流上前后两次接受到数据的间隔,单位为毫秒。
receiveTimeout: 30 * 1000,
// 请求的Content-Type,默认值是"application/json; charset=utf-8",Headers.formUrlEncodedContentType会自动编码请求体.
contentType: Headers.jsonContentType,
// 表示期望以那种格式(方式)接受响应数据。接受4种类型 `json`, `stream`, `plain`, `bytes`. 默认值是 `json`,
responseType: ResponseType.json,
);
_dio = Dio(options);
// 添加cookie
_addCookie();
// 添加缓存拦截器
dio.interceptors.add(NetCacheInterceptor());
// 在调试模式下
if (Config.isDebug) {
// 添加日志输出拦截器
_dio.interceptors
.add(LogInterceptor(requestBody: true, responseBody: true));
// 添加错误处理拦截器
_dio.interceptors.add(ErrorInterceptor());
// 在调试模式下需要抓包调试,所以我们使用代理,并禁用HTTPS证书校验
if (Config.proxyEnable) {
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
client.findProxy = (uri) {
return 'PROXY ${Config.proxyIp}:${Config.proxyPort}';
};
//代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
};
}
}
}
/// 加载cookie
_addCookie() async {
Directory appDocDir = await getApplicationDocumentsDirectory();
PersistCookieJar _cookieJar =
PersistCookieJar(storage: FileStorage(appDocDir.path + '/.cookies/'));
_dio.interceptors.add(CookieManager(_cookieJar));
}
/// get请求
Future get(url,
{Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken}) async {
url = BaseParam.buildUrl(url);
Map<String, dynamic> map = BaseParam.getBaseMap();
if (null != queryParameters) {
map.addAll(queryParameters);
}
Response? response;
try {
response = await _dio.get(url,
queryParameters: map,
options: options ?? Options(extra: {HttpKey.keyCacheEnable: false}),
cancelToken: cancelToken ?? _cancelToken);
} on DioError catch (_) {}
return response;
}
/// post请求
Future post(url,
{data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken}) async {
url = BaseParam.buildUrl(url);
Map<String, dynamic> map = BaseParam.getBaseMap();
if (null != data) {
map.addAll(data);
}
Response? response;
try {
response = await _dio.post(url,
data: map,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken ?? _cancelToken);
} on DioError catch (_) {}
return response;
}
/// post请求(表单提交操作)
Future postForm(url,
{Map<String, dynamic>? data,
Options? options,
CancelToken? cancelToken}) async {
url = BaseParam.buildUrl(url);
Map<String, dynamic> map = BaseParam.getBaseMap();
if (null != data) {
map.addAll(data);
}
Response? response;
try {
response = await _dio.post(url,
data: FormData.fromMap(map),
options: options,
cancelToken: cancelToken ?? _cancelToken);
} on DioError catch (_) {}
return response;
}
/// 取消请求,同一个cancel token 可以用于多个请求
/// 当一个cancel token取消时,所有使用该cancel token的请求都会被取消
void cancelRequests({CancelToken? token}) {
token ?? _cancelToken.cancel('cancelled');
}
}
ErrorInterceptor类
错误信息拦截,并通过日志打印输出(仅debug模式下生效)
import 'package:dio/dio.dart';
import 'package:flutter_common/util/log_util.dart';
/// 错误处理拦截器
class ErrorInterceptor extends Interceptor {
@override
void onError(DioError err, ErrorInterceptorHandler handler) {
AppException appException = AppException.create(err);
// 错误日志输出
LogUtil.log(appException.toString());
err.error = appException;
super.onError(err, handler);
}
}
/// 自定义异常
class AppException implements Exception {
final int code;
final String message;
AppException({
required this.code,
required this.message,
});
@override
String toString() {
return '错误代码:$code\n错误信息:$message';
}
factory AppException.create(DioError error) {
switch (error.type) {
case DioErrorType.cancel:
{
return _BadRequestException(-10, '请求取消');
}
case DioErrorType.connectTimeout:
{
return _BadRequestException(-11, '连接超时');
}
case DioErrorType.sendTimeout:
{
return _BadRequestException(-12, '请求超时');
}
case DioErrorType.receiveTimeout:
{
return _BadRequestException(-13, '响应超时');
}
case DioErrorType.response:
{
try {
int? errCode = error.response?.statusCode;
if (null != errCode) {
switch (errCode) {
case 400:
{
return _BadRequestException(errCode, '请求语法错误');
}
case 401:
{
return _UnauthorisedException(errCode, '没有权限');
}
case 403:
{
return _UnauthorisedException(errCode, '服务器拒绝执行');
}
case 404:
{
return _UnauthorisedException(errCode, '无法连接服务器');
}
case 405:
{
return _UnauthorisedException(errCode, '请求方法被禁止');
}
case 500:
{
return _UnauthorisedException(errCode, '服务器内部错误');
}
case 502:
{
return _UnauthorisedException(errCode, '无效的请求');
}
case 503:
{
return _UnauthorisedException(errCode, '服务器挂了');
}
case 505:
{
return _UnauthorisedException(errCode, '不支持HTTP协议请求');
}
default:
{
return AppException(
code: errCode,
message: '${error.response?.statusMessage}');
}
}
} else {
return AppException(code: -1, message: '未知错误');
}
} on Exception catch (_) {
return AppException(code: -2, message: '未知错误');
}
}
default:
{
return AppException(code: -3, message: error.message);
}
}
}
}
/// 请求错误
class _BadRequestException extends AppException {
_BadRequestException(int code, String message)
: super(code: code, message: message);
}
/// 未认证异常
class _UnauthorisedException extends AppException {
_UnauthorisedException(int code, String message)
: super(code: code, message: message);
}
NetCacheInterceptor类
网络缓存拦截器,默认不使用,可指定接口启用该功能。若开启磁盘缓存,会优先使用内存缓存
import 'dart:collection';
import 'package:dio/dio.dart';
import 'package:flutter_common/config/http_key.dart';
import 'package:flutter_common/util/date_util.dart';
import 'package:flutter_common/util/sp_util.dart';
// 缓存最大数量
const int _cacheMaxCount = 1000;
// 缓存最大默认时间(内存缓存)
const int _cacheMaxAge = 60 * 60 * 2;
/// 网络缓存拦截器
class NetCacheInterceptor extends Interceptor {
// 为确保迭代器顺序和对象插入时间一致顺序一致,我们使用LinkedHashMap
LinkedHashMap cacheLinkedHashMap = LinkedHashMap<String, CacheObject>();
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// 是否开启缓存(默认不开启)
bool cacheEnable = options.extra[HttpKey.keyCacheEnable] ?? false;
if (!cacheEnable) {
return super.onRequest(options, handler);
}
// refresh标记是否是刷新缓存(默认不刷新)
bool refresh = options.extra[HttpKey.keyRefresh] ?? false;
// 是否磁盘缓存(默认是不缓存)
bool cacheDisk = options.extra[HttpKey.keyCacheDiskEnable] ?? false;
// 如果刷新,先删除相关缓存
if (refresh) {
// 删除uri相同的内存缓存
delete(options.uri.toString());
// 删除磁盘缓存
if (cacheDisk) {
SPUtil().removeKey(options.uri.toString());
}
// 继续执行网络请求
return super.onRequest(options, handler);
}
// 如果是 get 请求,策略:内存缓存优先,然后才是磁盘缓存
if (options.method.toLowerCase() == 'get') {
String key = options.uri.toString();
// 1 内存缓存
var ob = cacheLinkedHashMap[key];
if (ob != null) {
// 若缓存未过期,则返回缓存内容
int cacheMaxAge = options.extra[HttpKey.keyCacheMaxAge] ?? _cacheMaxAge;
if ((DateUtil.currentTimeMillis() - ob.timeStamp) / 1000 <
cacheMaxAge) {
CacheObject cacheObject = cacheLinkedHashMap[key];
// 解析自定义数据(返回内存缓存数据)
return handler.resolve(cacheObject.response);
} else {
// 若已过期则删除缓存,继续向服务器请求
cacheLinkedHashMap.remove(key);
}
} else if (cacheDisk) {
// 磁盘缓存(有效期:永久)
String cacheData = SPUtil().getString(key);
if (cacheData.isNotEmpty) {
// 解析自定义数据(返回磁盘缓存数据)
return handler.resolve(Response(
statusCode: 200, data: cacheData, requestOptions: options));
}
}
}
return super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
// 如果启用缓存,将返回结果保存到缓存
RequestOptions options = response.requestOptions;
bool cacheEnable = options.extra[HttpKey.keyCacheEnable] ?? false;
if (cacheEnable) {
_saveCache(response);
}
return super.onResponse(response, handler);
}
/// 保持缓存
Future<void> _saveCache(Response response) async {
RequestOptions options = response.requestOptions;
// 只缓存 get 的请求,策略:内存、磁盘都写缓存
if (options.method.toLowerCase() == 'get') {
// 缓存key
String key = options.uri.toString();
// 磁盘缓存
if (options.extra[HttpKey.keyCacheDiskEnable] == true) {
SPUtil().put(key, response.data);
}
// 内存缓存,如果缓存数量超过最大数量限制,则先移除最早的一条记录
if (cacheLinkedHashMap.length == _cacheMaxCount) {
cacheLinkedHashMap
.remove(cacheLinkedHashMap[cacheLinkedHashMap.keys.first]);
}
cacheLinkedHashMap[key] = CacheObject(response);
}
}
void delete(String key) {
cacheLinkedHashMap.remove(key);
}
}
/// 缓存类
class CacheObject {
int timeStamp;
Response response;
CacheObject(this.response)
: timeStamp = DateTime.now().millisecondsSinceEpoch;
@override
bool operator ==(other) {
return response.hashCode == other.hashCode;
}
@override
int get hashCode => response.realUri.hashCode;
}