一、前言
网络请求是我们App开发过程中的重要内容,大部分App都需要和服务器进行数据交互,因此在开发过程中,我们需要封装我们网络请求的代码,对request、response以及error做统一处理,减少业务开发中的样板代码。
二、使用dio进行网络通讯
2.1 配置网络请求的基本信息
关于dio的具体用法参考dio官方文档。按照下面方式配置dio package:
dependencies:
dio: ^4.0.4
在网络请求中,我们通常需要配置服务器的地址以及超时等,这里我们新建一个http_config.dat文件,添加如下内容:
class HttpConfig {
//接口基础地址
static const baseUrl = 'https://httpbin.org';
static const connectTimeout = 5000;
static const receiveTimeout = 3000;
//基础配置
static final options = BaseOptions(
baseUrl: baseUrl,
connectTimeout: connectTimeout,
receiveTimeout: receiveTimeout);
}
其他具体业务中关于网络请求的通用配置信息都可以在这里进行添加,这里只是一个参考方式。
2.2 单例Dio对象
dio的作者也提示我们,尽量使用单例模式,我们可以按下面的方式定义单例:
class DioManager {
static final DioManager _instance = DioManager._internal();
factory DioManager() => _instance;
late Dio dio;
DioManager._internal(){
_initDio();
}
///
///
/// 完成DIO的基础配置
///
void _initDio() {
dio = Dio(HttpConfig.options);
dio.interceptors.add(HeaderInterceptor());
dio.interceptors.add(AccessTokenInterceptor());
dio.interceptors.add(LogInterceptor());
}
}
2.3 处理网络请求
处理网络请求可以单独定义一个Http_util.dart文件,内容封装网络常用的请求方法,比如get、post等。
这里参考以前Android官方的设计方法,定义一个model作为最终的返回对象,resource.dart
//业务code成功
const codeResponseSuccess = 1;
//其他未知错误
const codeErrorUnknown = -1;
///
/// 参考以前的Android架构设计方案,设计Resource统一返回值
///
///
class Resource {
int code;
String? msg;
dynamic data;
///
/// 业务请求成功,服务端返回业务code值为codeResponseSuccess
///
Resource.success(this.data, {this.code = codeResponseSuccess});
///
/// 业务code不为success或者其他错误
///
Resource.error(this.msg, this.code);
///
///
/// 业务流程是否正常
///
bool isSuccess(){
return code == codeResponseSuccess;
}
解析来定义一个http_util.dart来处理网络请求的具体操作,如下所示:
const String methodPost = 'post';
const String methodGet = 'get';
class HttpUtil {
///
/// get 请求
///
///
static Future<Resource> get(String path,
{Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress}) async {
return _sendRequest(methodGet, path, queryParameters, options, cancelToken,
onReceiveProgress);
}
///
/// post 请求
///
///
static Future<Resource> post(
String path, {
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
}) {
return _sendRequest(methodPost, path, queryParameters, options, cancelToken,
onReceiveProgress);
}
///
///统一发送请求预计返回值统一处理
///
static Future<Resource> _sendRequest(
String method,
String path,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
) async {
var dio = DioManager().dio;
try {
Response? rsp;
if (method == methodGet) {
rsp = await dio.get(path, queryParameters: queryParameters);
} else if (method == methodPost) {
rsp = await dio.post(path, data: queryParameters);
}
if (rsp == null) return Resource.error('未知错误', codeErrorUnknown);
return _handleResponse(rsp);
} catch (err) {
return Resource.error(_handleException(err), codeErrorUnknown);
}
}
}
_sendRequest的处理需要注意异常的处理,针对await的方式进行操作的,需要使用try/catch进行捕获异常。关于Future的理解和异常处理可参考:Flutter-了解Future及其用法
处理返回值:
返回值的处理需要根据服务定义的规范进行处理,这里使用的 https://httpbin.org 返回的数据格式进行解析处理:
///
/// 统一处理解析数据
///
static Resource _handleResponse(Response response) {
if (_isSuccess(response.statusCode)) {
var data = response.data['args'];
var code = int.parse(data['code']);
String msg = data['message'];
if (code == codeResponseSuccess) {
//这里尽量将业务中的code丢出去,方便开发中需要根据该code来进行一些特殊的处理
return Resource.success(data['data'],code: code);
} else {
return Resource.error(msg, code);
}
} else {
return Resource.error('未知错误', codeErrorUnknown);
}
}
///状态码是否成功
static bool _isSuccess(int? statusCode) {
return (statusCode != null && statusCode >= 200 && statusCode < 300);
}
处理异常:
异常也是重要的一部分,可按照下面的方式进行异常处理,用于信息提示。
///
/// 统一处理异常-返回错误信息
///
static String _handleException(ex) {
if (ex is DioError) {
switch (ex.type) {
case DioErrorType.connectTimeout:
case DioErrorType.sendTimeout:
case DioErrorType.receiveTimeout:
return '网络超时';
case DioErrorType.other:
return '未知错误';
case DioErrorType.response:
int? statusCode = ex.response?.statusCode;
switch (statusCode) {
case 400:
return '请求语法错误';
case 401:
return '没有权限';
case 403:
return '服务器拒绝执行';
case 404:
return '请求资源部存在';
case 405:
return '请求方法被禁止';
case 500:
return '服务器内部错误';
case 502:
return '无效请求';
case 503:
return '服务器异常';
default:
return '未知错误';
}
default:
return '未知错误';
}
} else {
return '未知错误';
}
}
2.3 业务中调用
在业务中调用就相对简单点了,直接使用HttpUtil.get或者post即可,如下:
_testGetNedData() async {
var param = {'code': 1, 'message': 'success', 'data': 'test net data'};
var resource = await HttpUtil.get('/get', queryParameters: param);
if (resource.isSuccess()) {
//TODO 处理其他业务流程
var data = resource.data;
print('请求成功结果:$data');
} else {
print('请求错误:${resource.msg}');
}
}
//或者
_testGetNedData2() {
var param = {'code': 1, 'message': 'success', 'data': 'test net data'};
var resource = HttpUtil.get('/get', queryParameters: param);
resource.then((value) {
if (value.isSuccess()) {
//TODO 处理其他业务流程
var data = value.data;
print('请求成功结果:$data');
} else {
print('请求错误:${value.msg}');
}
});
}
根据以前Retrofit的写法,我们通常根据业务定义一个Service文件,统一封装和处理网络请求这块。这里我们定义个test_service.dart文件,如下:
///
/// 测试模块的统一请求处理
///
class TestService{
///
///业务1
///
static Future<Resource> testOne(Map<String,dynamic> param){
return HttpUtil.get('/get',queryParameters: param);
}
///
///业务2
///
static Future<Resource> testTwo(Map<String,dynamic> param){
return HttpUtil.get('/get',queryParameters: param);
}
}
接下来就可以在业务模块中直接调动该处理方法:
_testGetNedData2() {
var param = {'code': 1, 'message': 'success', 'data': 'test net data'};
var resource = TestService.testOne(param);
resource.then((value) {
if (value.isSuccess()) {
//TODO 处理其他业务流程
var data = value.data;
print('请求成功结果:$data');
} else {
print('请求错误:${value.msg}');
}
});
}
这样做的好处就是方便后期的维护,后面上手的人可以根据该文件找到业务模块的网络请求方法,快速的维护。
最后
- 上述的实例展示基于dio的封装的业务调用方法的一部分,完成了一些基础的网络通讯功能,具体项目中使用,需要具体要求进行跳转,特别是http_util.dart中的_handleResponse进行修改
- 但是上述的封装还不能满足具体的项目要求,比如token和refreshToken的处理,以及其他一些通用的信息添加,这些放在后面的拦截器中进行处理。
|