https://github.com/cfug/dio/blob/main/dio/README-ZH.md
手动添加到pubspec.yaml:
dependencies:
dio: ^替换为最新版本
在终端使用以下命令:
$ dart pub add dio
dio 是一个强大的 HTTP 网络请求库,支持全局配置、Restful API、FormData、拦截器、 请求取消、Cookie 管理、文件上传/下载、超时、自定义适配器、转换器等。
单例模式详见:Flutter之单例模式的四种方法_YUFENGSHI.LJ的博客-CSDN博客
class HttpManager{
//1、通过静态方法 getInstance() 访问实例—————— getInstance() 构造、获取、返回实例
/*通过工厂方法获取该类的实例,将实例对象按对应的方法返回出去
*实例不存在时,调用命名构造方法获取一个新的实例 */
static HttpManager getInstance(){
if(_instance==null){
_instance=HttpManager._internal();
}
return _instance!;
}
//2、静态属性——该类的实例
static HttpManager? _instance=HttpManager._internal();
//3、私有的命名构造函数,确保外部不能拿到它————初始化实例
HttpManager._internal(){}
//4.1、创建一个 Dio 实例
late Dio dio;
可以使用默认配置或传递一个可选 BaseOptions
参数来创建一个Dio实例
BaseOptions
描述的是 Dio 实例发起网络请求的的公共配置,而 Options
描述了每一个Http请求的配置信息,每一次请求都可以单独配置。
单次请求的 Options
中的配置信息可以覆盖 BaseOptions
中的配置。
BaseOptions
:基类请求配置
//请求方式
String? method,
//连接超时时间
Duration? connectTimeout,
//接收超时
Duration? receiveTimeout,
//发送超时
Duration? sendTimeout,
//基本网址
String baseUrl = '',
//请求包头
Map? headers,
//以何种方式接收响应数据,默认是json
ResponseType? responseType = ResponseType.json,
//内容类型
String? contentType,
Map? queryParameters,
Map? extra,
ValidateStatus? validateStatus,
bool? receiveDataWhenStatusError,
bool? followRedirects,
int? maxRedirects,
bool? persistentConnection,
RequestEncoder? requestEncoder,
ResponseDecoder? responseDecoder,
ListFormat? listFormat,
Options
: 单次请求配置
/// 请求方式。
String method;
/// 请求基本地址,可以包含路径例如 https://dart.dev/api/。
String? baseUrl;
/// HTTP 请求头。
Map? headers;
/// 连接服务器超时时间.
Duration? connectTimeout;
/// 两次数据流数据接收的最长间隔时间,注意不是请求的最长接收时间。
Duration? receiveTimeout;
/// 请求内容体,可以是任意类型。
dynamic data;
/// 请求路径,如果以 http(s)开始, 则 [baseURL] 会被忽略,
/// 否则将会和 [baseUrl] 拼接出完整的地址。
String path = '';
/// 请求的 Content-Type。
///
/// 默认值会由 [ImplyContentTypeInterceptor] 根据请求载荷类型进行推导。
/// 可以调用 [Interceptors.removeImplyContentTypeInterceptor] 进行移除。
///
/// 如果你想以 `application/x-www-form-urlencoded` 格式编码请求数据,
/// 可以设置此选项为 `Headers.formUrlEncodedContentType`,
/// [Dio] 会自动编码请求体。
String? contentType;
/// 期望以哪种格式(方式)接受响应数据,包括 `json`、`stream` 和 `plain`。
///
/// 默认值是 `json`, 当响应头中 content-type 为 `application/json` 时,
/// dio 会自动将响应内容转化为 json 对象。
/// 如果想以二进制方式接受响应数据,如下载一个二进制文件,那么可以使用 `stream`。
///
/// 如果想以文本(字符串)格式接收响应数据,请使用 `plain`。
ResponseType? responseType;
/// `validateStatus` 决定 HTTP 响应状态码是否被视为请求成功,
/// 返回 `true` 请求结果就会按成功处理,否则会按失败处理.
ValidateStatus? validateStatus;
/// 用户自定义字段,可以在 [Interceptor]、[Transformer] 和 [Response] 中依次传递。
Map? extra;
/// 请求地址的参数。
Map*/ >? queryParameters;
/// 请求数据中数组的编码的方式,默认值为 `multiCompatible`。
ListFormat? listFormat;
实际请求配置,即[BaseOptions]和[Options]组合后的最终结果。
/// The internal request option class that is the eventual result after
/// [BaseOptions] and [Options] are composed.
class RequestOptions extends _RequestConfig with OptionsMixin {
RequestOptions({
this.path = '',
this.data,
this.onReceiveProgress,
this.onSendProgress,
this.cancelToken,
String? method,
Duration? sendTimeout,
Duration? receiveTimeout,
Duration? connectTimeout,
Map? queryParameters,
String? baseUrl,
Map? extra,
Map? headers,
ResponseType? responseType,
String? contentType,
ValidateStatus? validateStatus,
bool? receiveDataWhenStatusError,
bool? followRedirects,
int? maxRedirects,
bool? persistentConnection,
RequestEncoder? requestEncoder,
ResponseDecoder? responseDecoder,
ListFormat? listFormat,
bool? setRequestContentTypeWhenNoPayload,
StackTrace? sourceStackTrace,
}) : assert(connectTimeout == null || !connectTimeout.isNegative),
super(
method: method,
sendTimeout: sendTimeout,
receiveTimeout: receiveTimeout,
extra: extra,
headers: headers,
responseType: responseType,
contentType: contentType,
validateStatus: validateStatus,
receiveDataWhenStatusError: receiveDataWhenStatusError,
followRedirects: followRedirects,
maxRedirects: maxRedirects,
persistentConnection: persistentConnection,
requestEncoder: requestEncoder,
responseDecoder: responseDecoder,
listFormat: listFormat,
) {
this.sourceStackTrace = sourceStackTrace ?? StackTrace.current;
this.queryParameters = queryParameters ?? {};
this.baseUrl = baseUrl ?? '';
this.connectTimeout = connectTimeout;
}
/// Create a [RequestOptions] from current instance with merged attributes.
RequestOptions copyWith({
String? method,
Duration? sendTimeout,
Duration? receiveTimeout,
Duration? connectTimeout,
dynamic data,
String? path,
Map? queryParameters,
String? baseUrl,
ProgressCallback? onReceiveProgress,
ProgressCallback? onSendProgress,
CancelToken? cancelToken,
Map? extra,
Map? headers,
ResponseType? responseType,
String? contentType,
ValidateStatus? validateStatus,
bool? receiveDataWhenStatusError,
bool? followRedirects,
int? maxRedirects,
bool? persistentConnection,
RequestEncoder? requestEncoder,
ResponseDecoder? responseDecoder,
ListFormat? listFormat,
bool? setRequestContentTypeWhenNoPayload,
}) {
final contentTypeInHeader = headers != null &&
headers.keys
.map((e) => e.toLowerCase())
.contains(Headers.contentTypeHeader);
assert(
!(contentType != null && contentTypeInHeader),
'You cannot set both contentType param and a content-type header',
);
final ro = RequestOptions(
method: method ?? this.method,
sendTimeout: sendTimeout ?? this.sendTimeout,
receiveTimeout: receiveTimeout ?? this.receiveTimeout,
connectTimeout: connectTimeout ?? this.connectTimeout,
data: data ?? this.data,
path: path ?? this.path,
baseUrl: baseUrl ?? this.baseUrl,
queryParameters: queryParameters ?? Map.from(this.queryParameters),
onReceiveProgress: onReceiveProgress ?? this.onReceiveProgress,
onSendProgress: onSendProgress ?? this.onSendProgress,
cancelToken: cancelToken ?? this.cancelToken,
extra: extra ?? Map.from(this.extra),
headers: headers ?? Map.from(this.headers),
responseType: responseType ?? this.responseType,
validateStatus: validateStatus ?? this.validateStatus,
receiveDataWhenStatusError:
receiveDataWhenStatusError ?? this.receiveDataWhenStatusError,
followRedirects: followRedirects ?? this.followRedirects,
maxRedirects: maxRedirects ?? this.maxRedirects,
persistentConnection: persistentConnection ?? this.persistentConnection,
requestEncoder: requestEncoder ?? this.requestEncoder,
responseDecoder: responseDecoder ?? this.responseDecoder,
listFormat: listFormat ?? this.listFormat,
sourceStackTrace: sourceStackTrace,
);
if (contentType != null) {
ro.headers.remove(Headers.contentTypeHeader);
ro.contentType = contentType;
} else if (!contentTypeInHeader) {
ro.contentType = this.contentType;
}
return ro;
}
/// The source [StackTrace] which should always point to the invocation of
/// [DioMixin.request] or if not provided, to the construction of the
/// [RequestOptions] instance. In both instances the source context should
/// still be available before it is lost due to asynchronous operations.
@internal
StackTrace? sourceStackTrace;
/// Generate the requesting [Uri] from the options.
Uri get uri {
String url = path;
if (!url.startsWith(RegExp(r'https?:'))) {
url = baseUrl + url;
final s = url.split(':/');
if (s.length == 2) {
url = '${s[0]}:/${s[1].replaceAll('//', '/')}';
}
}
final query = Transformer.urlEncodeQueryMap(queryParameters, listFormat);
if (query.isNotEmpty) {
url += (url.contains('?') ? '&' : '?') + query;
}
// Normalize the url.
return Uri.parse(url).normalizePath();
}
/// Request data in dynamic types.
dynamic data;
/// Defines the path of the request. If it starts with "http(s)",
/// [baseUrl] will be ignored. Otherwise, it will be combined and resolved
/// with the [baseUrl].
String path;
/// {@macro dio.CancelToken}
CancelToken? cancelToken;
/// {@macro dio.options.ProgressCallback}
ProgressCallback? onReceiveProgress;
/// {@macro dio.options.ProgressCallback}
ProgressCallback? onSendProgress;
}
/*在私有构造方法中,在里面可以进行初始化dio实例*/
HttpManager._internal(){
//4.2、设置BaseOptions
BaseOptions baseOptions=BaseOptions(
//基本网址
baseUrl:"https://lionstock-uat-new.chinaeast2.cloudapp.chinacloudapi.cn:8200/",
//连接超时
connectTimeout: Duration(milliseconds: 30000),
//接收超时
receiveTimeout: Duration(milliseconds: 5000),
//包头
headers: {
"Content-Type": "application/json;Charset=UTF-8",
"connect":"get"
},
//内容类型
contentType: 'application/json;Charset=UTF-8',
//响应类型——期待已那种方式接收数据,默认是json
responseType: ResponseType.json,
);
//4.3 初始化dio实例
dio=new Dio(baseOptions) ;
//添加一个拦截器
dio.interceptors.add(new DioLogInterceptor());
}
当请求成功时会返回一个Response对象,它包含如下字段:
/// 响应数据。可能已经被转换了类型, 详情请参考 [ResponseType]。
T? data;
/// 响应对应的实际请求配置。
RequestOptions requestOptions;
/// 响应的 HTTP 状态码。
int? statusCode;
/// 响应对应状态码的详情信息。
String? statusMessage;
/// 响应是否被重定向
bool isRedirect;
/// 请求连接经过的重定向列表。如果请求未经过重定向,则列表为空。
List redirects;
/// 在 [RequestOptions] 中构造的自定义字段。
Map extra;
/// 响应对应的头数据(响应头)
Headers headers;
get方法中只有路径是必填的,
Future> get(
String path, {
Object? data,
Map? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
});
get
方法使用指定的路径和查询参数向服务器发送 HTTP GET 请求。它还允许您传递其他选项,例如标头、响应类型和超时。该方法返回一个 Future 对象,该对象使用包含来自服务器的 HTTP 响应的 Response 对象进行解析。
get(String url,{option,params}) async {
Response response;
try{
response=await dio.get(url,options: Options(responseType: ResponseType.json));
print("response.data:${response.data}");
print("response.data:${response.statusCode}");
print("response.data:${response.statusMessage}");
print("response.data:${response.headers}");
}
on Exception catch(e){
print("Get方法出错:${e.toString()}");
}
}
接口:https://api.github.com/orgs/flutterchina/repos
HttpManager.getInstance().get(
"https://reqres.in/api/users",
option: Options(responseType: ResponseType.plain),
);
// 获取id 法一
HttpManager.getInstance().get("https://reqres.in/api/users/1");
// 获取id 法二
HttpManager.getInstance().get("https://reqres.in/api/users?id=2");
// 获取id 法三 用参数类型
Map map = Map();
map["id"]= 3;
HttpManager.getInstance().get(
"https://reqres.in/api/users",
option: Options(responseType: ResponseType.json),
params:map
);
在发出 GET 请求时,我们通常不传递任何数据。但是当发出 POST、PUT、DELETE 等请求时,我们需要传递正文/数据。
post
方法与方法类似get
,但增加了一个data
参数,该参数代表请求正文。使用该方法获取请求标头getAuthorizationHeader
并与提供的任何选项合并。使用发出请求dio.post
,如果成功则返回响应数据。如果请求由于 Dio 错误而失败,ErrorEntity
则会抛出异常。
post(api,{params}) async {
Response response;
//请求参数 为空时,配置
if(params==null){
params["marketNo"] = "PC_Flutter";
params["versionNo"] = '10105';/*版本号*/
params["token"] = '6b2fc908787c428ab16559fce9d86bf2';
params["uid"] = '201323';
}
try{
response=await dio.post(
api,
queryParameters: params,
);
print("post response:${response.data}\n");
}
on Exception catch (e){
print("post出错:${e.toString()}");
}
}
HttpManager.getInstance().post(
"https://www.wanandroid.com/user/register",
params: {
"username": "zsdhwiehfwo",
"password": "123456",
"repassword": "123456"}
);
post response:{data: {admin: false, chapterTops: [], coinCount: 0, collectIds: [], email: , icon: , id: 151550, nickname: zsdhwiehfwo, password: , publicName: zsdhwiehfwo, token: , type: 0, username: zsdhwiehfwo}, errorCode: 0, errorMsg: }
每个 Dio 实例都可以添加任意多个拦截器,他们会组成一个队列,拦截器队列的执行顺序是先进先出。 通过使用拦截器,你可以在请求之前、响应之后和发生异常时(未被 then
或 catchError
处理) 做一些统一的预处理操作。
/// Deliver the [response] to the next interceptor.
///
/// Typically, the method should be called once interceptors done
/// manipulating the [response].
///将[响应]传递给下一个拦截器。通常,一旦拦截器完成操作[响应],就应该调用该方法。
void next(Response response) {
_completer.complete(
InterceptorState(response),
);
_processNextInQueue?.call();
}
如果不调用handler.next(response)方法,那么请求将被中止,也就是说后续的拦截器和回调函数将不会被执行。
/// Called when the request is about to be sent.
void onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) {
handler.next(options);
}
RequestOptions options,表示本次请求的实际配置
RequestInterceptorHandler是一个拦截器处理器,用于处理请求拦截器中的逻辑。
/// The handler for interceptors to handle after respond.
class ResponseInterceptorHandler extends _BaseHandler {
/// Deliver the [response] to the next interceptor.
///
/// Typically, the method should be called once interceptors done
/// manipulating the [response].
void next(Response response) {
_completer.complete(
InterceptorState(response),
);
_processNextInQueue?.call();
}
/// Completes the request by resolves the [response] as the result.
void resolve(Response response) {
_completer.complete(
InterceptorState(
response,
InterceptorResultType.resolve,
),
);
_processNextInQueue?.call();
}
/// Completes the request by reject with the [error] as the result.
///
/// Invoking the method will make the rest of interceptors in the queue
/// skipped to handle the request,
/// unless [callFollowingErrorInterceptor] is true
/// which delivers [InterceptorResultType.rejectCallFollowing]
/// to the [InterceptorState].
void reject(DioException error,
[bool callFollowingErrorInterceptor = false]) {
_completer.completeError(
InterceptorState(
error,
callFollowingErrorInterceptor
? InterceptorResultType.rejectCallFollowing
: InterceptorResultType.reject,
),
error.stackTrace,
);
_processNextInQueue?.call();
}
}
/// Called when the response is about to be resolved.
///当响应即将解决时调用。
void onResponse(
Response response,
ResponseInterceptorHandler handler,
) {
handler.next(response);
}
response表示响应数据,包括响应状态码、响应头、响应数据等
ResponseInterceptorHandler是一个拦截器处理器,用于处理响应拦截器中的逻辑。
/// The handler for interceptors to handle after respond.
class ResponseInterceptorHandler extends _BaseHandler {
/// Deliver the [response] to the next interceptor.
///
/// Typically, the method should be called once interceptors done
/// manipulating the [response].
void next(Response response) {
_completer.complete(
InterceptorState(response),
);
_processNextInQueue?.call();
}
/// Completes the request by resolves the [response] as the result.
void resolve(Response response) {
_completer.complete(
InterceptorState(
response,
InterceptorResultType.resolve,
),
);
_processNextInQueue?.call();
}
/// Completes the request by reject with the [error] as the result.
///
/// Invoking the method will make the rest of interceptors in the queue
/// skipped to handle the request,
/// unless [callFollowingErrorInterceptor] is true
/// which delivers [InterceptorResultType.rejectCallFollowing]
/// to the [InterceptorState].
void reject(DioException error,
[bool callFollowingErrorInterceptor = false]) {
_completer.completeError(
InterceptorState(
error,
callFollowingErrorInterceptor
? InterceptorResultType.rejectCallFollowing
: InterceptorResultType.reject,
),
error.stackTrace,
);
_processNextInQueue?.call();
}
}
/// Called when an exception was occurred during the request.
///当请求过程中发生异常时调用。
void onError(
DioException err,
ErrorInterceptorHandler handler,
) {
handler.next(err);
}
class DioLogInterceptor extends Interceptor{
///请求前
@override
Future onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
String requestStr = "\n==================== 请求前拦截——REQUEST ====================\n"
"- URL:\n${options.baseUrl + options.path}\n"
"- METHOD: ${options.method}\n";
requestStr += "- HEADER:\n${options.headers.mapToStructureString()}\n";
final data = options.data;
if (data != null) {
if (data is Map)
requestStr += "- BODY:\n${data.mapToStructureString()}\n";
else if (data is FormData) {
final formDataMap = Map()..addEntries(data.fields)..addEntries(data.files);
requestStr += "- BODY:\n${formDataMap.mapToStructureString()}\n";
} else
requestStr += "- BODY:\n${data.toString()}\n";
}
print(requestStr);
return handler.next(options);
}
}
//添加一个拦截器
dio.interceptors.add(new DioLogInterceptor());
当请求过程中发生错误时, Dio 会将 Error/Exception
包装成一个 DioException
: