dio
详细中文文档点这里查看
执行流
请求拦截器Interceptor >> 请求转换器Transformer >> 发起请求 >> 响应转换器Transformer >> 响应拦截器Interceptor >> 最终结果。
拦截器Interceptor
Interceptor有三个方法,分别表示请求拦截onRequest,响应拦截onResponse,错误拦截onError.
一个dio对象会有多个Interceptor,它们的执行顺序是FIFO,先加入的拦截器会先执行,后面的拦截器,根据前面拦截器的InterceptorHandler对象的调用方法来判断执行操作;
一般不在
Interceptor流程里面修改请求返回的数据格式,如果需要修改,可以在Transformer里面进行处理
QueuedInterceptor序列拦截器
如果有多个并发请求,则在进入拦截器之前将请求添加到队列中。每次只有一个请求进入拦截器,在该请求被拦截器处理后,下一个请求将进入拦截器。不调用InterceptorHandler的方法时,其它请求会一直等待.
使用场景:请求前判断token是否为空,为空的话请求token并插入,再执行next.
class Interceptors extends ListMixin<Interceptor> {
final _list = <Interceptor>[];
final Lock _requestLock = Lock();
final Lock _responseLock = Lock();
final Lock _errorLock = Lock();
根据代码看,每个流程都有单独的锁,每个dio对象,都有单独的Interceptors实例,各个流程之间互不影响,各个dio对象之间,也互不影响?(待确认)
拦截器的流程控制器 InterceptorHandler
InterceptorHandler有三个方法: next,resolve,reject.
next
继续执行下个拦截器的相同拦截方法,比如在onRequest里面调用handler.next(options),那么下一个拦截器的onRequest也会被执行,依次下去.
通常会调用这个,便于拦截器依次执行,避免出现某些拦截器没被执行导致出错.
resolve
结束拦截器流,直接返回结果;
RequestInterceptorHandler 调用resolve时,可以传入callFollowingResponseInterceptor = true来控制是否执行后续拦截器的onResponse方法.
比如get请求的缓存,可以在
onRequest里面判断是否有缓存,如果有,直接调用resolve,返回缓存的结果,而不用发起请求.
reject
抛出错误 ,结束拦截器流程,此时请求流程也会结束,抛出错误
RequestInterceptorHandler和ResponseInterceptorHandler 调用reject时,可以传入callFollowingErrorInterceptor = true来控制是否执行后续拦截器的onError方法.
比如发起请求前先进行网络环境判断,如果没有网络,则直接调用
reject,抛出错误,略过dio执行流的过程. 比如收到请求后,判断response.data里面的服务器请求码,如果失败则使用reject,将response流程转到error流程.
转换器Transformer
dio自带了一个转换器DefaultTransformer,它处理了请求时数据的编码,和请求响应数据的解析.
- 处理
RequestOptions里的data数据,将其它类型的data转换成String - 处理请求收到的
ResponseBody,接收完整的二进制流ResponseBody.stream
DefaultTransformer 预留了一个属性jsonDecodeCallback,可以自定义把数据从string转换成dynamic类型的过程:
typedef JsonDecodeCallback = dynamic Function(String);
JsonDecodeCallback? jsonDecodeCallback;
如果不提供jsonDecodeCallback属性,那么默认会使用json.decode(responseBody)来转换.
if (responseBody.isNotEmpty &&
options.responseType == ResponseType.json &&
Transformer.isJsonMimeType(
response.headers[Headers.contentTypeHeader]?.first)) {
final callback = jsonDecodeCallback;
if (callback != null) {
return callback(responseBody);
} else {
return json.decode(responseBody);
}
}
仅针对json类型的数据才会调用
jsonDecodeCallback进行处理.
使用场景:
- 超大json解析时,会占用
main isolate导致UI卡顿,这个时候可以使用compute函数来进行json解析;同时dio文档上有注明,使用compute会导致json解析耗时变长. - 可以直接将json字符串解析成请求响应的base模型数据(
BaseEntity<T>).
实际使用时,不能一步到位直接拿到解析数据,只能拿到BaseEntity<dynamic>类型的数据,然后拿到数据后还得进行第二次解析,将dynamic解析成想要的T.而由于jsonDecodeCallback不支持泛型,二次解析,还得在执行流的其他环节处理.这样会导致整个解析逻辑分散.
多文件上传
多文件上传时,通过给key加中括号[]方式作为文件数组的标记,大多数后台也会通过key[]这种方式来读取。不过RFC中并没有规定多文件上传就必须得加[],所以有时不带[]也是可以的,关键在于后台和客户端得一致。v3.0.0 以后通过Formdata.fromMap()创建的Formdata,如果有文件数组,是默认会给key加上[]的,比如:
FormData.fromMap({
'files': [
MultipartFile.fromFileSync('./example/upload.txt', filename: 'upload.txt'),
MultipartFile.fromFileSync('./example/upload.txt', filename: 'upload.txt'),
]
});
最终编码时会key会为 files[],如果不想添加[],可以通过Formdata的API来构建:
var formData = FormData();
formData.files.addAll([
MapEntry('files',
MultipartFile.fromFileSync('./example/upload.txt',filename: 'upload.txt'),
),
MapEntry('files',
MultipartFile.fromFileSync('./example/upload.txt',filename: 'upload.txt'),
),
]);
这样构建的FormData的key是不会有[]。
json解析库
用于将后端返回的json数据转换成dart对象
json_annotation: ^4.5.0
json_serializable: ^6.2.0
app的需求是,每次返回的最外层模型是一致的,只有data每次不一样,而且只有data字段才是业务字段,其它字段仅判断请求是否成功。这里需要使用泛型来进行转换。
手动使用太过麻烦,一般使用插件。而VSCode里面没有比较好的插件。
FlutterJsonBeanFactory 插件
Android Studio里面才有这个插件。处理json文件时最好使用Android Studio。
使用时直接将后台返回的json复制到插件页面,填好名称后就能自动生成对应的entity.dart和entity.g.dart文件。
通过插件生成文件之后,lib/generated/json/base文件夹下会生成两个文件:
json_convert_content.dart
这个文件专门负责将json数据(Map或者List)转换成泛型T或者[T],因为dart不支持反射,所以没法像其它语言那样使用反射来处理json。也就是说,传入的T需要调用T.fromJson()方法时,是没法通过反射来调用的。
json_convert_content提供了一个_convertFuncMap,专门存放模型T与对应的fromJson方法:
static final Map<String, JsonConvertFunction> _convertFuncMap = {
(AppVersionEntity).toString(): AppVersionEntity.fromJson,
};
这样在json转模型时,直接通过T.toString()作为key,来查找对应的fromJson方法。
_convertFuncMap只处理了Map -> Model的流程,如果json是一个List<Map>,显然没有List<T>.fromJson()方法可以调用。
json_convert_content提供了一个_getListChildType方法,专门负责将List<Map>转换成List<T>:
static M? _getListChildType<M>(List<Map<String, dynamic>> data) {
if(<AppVersionEntity>[] is M){
return data.map<AppVersionEntity>((Map<String, dynamic> e) => AppVersionEntity.fromJson(e)).toList() as M;
}
debugPrint("${M.toString()} not found");
return null;
}
使用插件时,插件会自动管理json_convert_content文件,将创建的Entity类型加入上面_convertFuncMap和_getListChildType里面,避免手动管理出现错漏。
json_field.dart
json序列化的工具类,决定json转模型的字段映射,以及是否映射
class JsonSerializable{
const JsonSerializable();
}
class JSONField {
//自定义映射名称
final String? name;
//转json时是否映射
final bool? serialize;
//转模型时是否映射
final bool? deserialize;
const JSONField({this.name, this.serialize, this.deserialize});
}
一般也不用自己处理,插件生成的文件自己会处理好,后续有特殊情况才需要手动修改。
示例:
@JsonSerializable() //标记这个类是json序列化来的
class AppVersionEntity {
int? id;
@JSONField(name: "source") //自定义映射名,json里面是source,模型里面是xSource
String? xSource;
String? version;
String? versionName;
String? url;
String? title;
String? updateContent;
bool? status;
String? channel;
String? gmtCreate;
String? gmtUpdate;
AppVersionEntity();
factory AppVersionEntity.fromJson(Map<String, dynamic> json) => $AppVersionEntityFromJson(json);
Map<String, dynamic> toJson() => $AppVersionEntityToJson(this);
@override
String toString() {
return jsonEncode(this);
}
}