flutter作为跨平台提效方案,确实可以将开发的效率大幅提升,前提是:
- 尽量少的桥接
- 尽量多的复用
从这两个出发点延伸,接入Flutter后,会承接更多的业务逻辑,增加代码的平台性。基于这个原则,flutter将会承接工程中的主力。网络库的构建,也成了不可缺少的工作。
这篇文章中不在介绍网络选型的逻辑,所有的代码都是基于DIO网络通道构建的。
网络框架分析
客户端在构建的时候,往往都会在网络层的使用上封装一层,常见的封装有:
- 基于函数的封装
- 面向协议封装
一、前者常见的调用方式为:
[***Network get:参数 success:成功回调 failure:失败回调];
一般都是类函数的方式调用,封装的主要体现在:
- 简单API的输出
- 针对业务功能的Header聚合和封装
- baseURL的收敛
- post get 通过api区分
- 共有参数的收集
- 失败和成功的统一处理
- 入口出口的统一处理
在调用的时候业务只需要准备好对应的参数即可。但是这种方案的设计也存在一些缺陷
- header的修改
- baseUrl的修改
- 请求的逻辑不够聚合,零散分布问题 (都是函数的调用,散落在不同的业务文件中)
所以这种方式未必是你需要的网络封装
二、后者常见的调用方式为
[[[***Instance alloc] initWithParam:参数] requestWithSuccess:成功回调 failure:失败回调];
这里存在一个显著的区别,最终调用的对象是一个实例,而不是一个函数,这里封装主要体现在
- 功能点的分离
- 逻辑的拆解
- 业务穿插的节点
- 网络传输通道的唯一性
功能落地
下面就来看看在Flutter中如何实现这一套方案,首先功能点的分离,对于网络对象而言,他是有自己的内部的结构的,我们将这样的内部结构抽象到一个虚类中
/*
* 网络接口协议定义
*/
abstract class HttpApi {
String baseURL;
String path;
String method;
Map<String, dynamic> parameters;
Map<String, String> headers;
ResultData result;
String get url => baseURL + path;
}
在使用的时候为了便于操作,url是直接通过baseURL + path的方式得到的。业务在使用的时候,会适配一些通用的配置,例、
/*
*业务适配器,在这里可以配置对应的baseURl 和 通用的header
*/
class BusinessApi extends HttpApi {
@override
// TODO: implement baseURL
String get baseURL => "https://yourDomain.com";
@override
// TODO: implement headers
Map<String, String> get headers => HttpHeader.defaultHeader;
}
这样,网络请求在不复写的情况下,都可以使用这两个默认值了。
为了让所有的网络请求都走到统一的网络通道中(方便之后的监控,入口和出口统一,可以做很多统计事件),需要将网络通道独立处理
/*
* 网络通道
*/
class NetworkPipe extends BusinessApi {
Future<ResultData> request<T> () async {
// 准备数据源
Map<String, String> headers = new HashMap();
// 默认的header需要加上
if (HttpHeader.defaultHeader != null) {
headers.addAll(HttpHeader.defaultHeader);
}
// 当前header
if (this.headers != null) {
headers.addAll(this.headers);
}
Options option = new Options(method: this.method);
///超时
option.connectTimeout = 15000;
///header
// option.headers = headers;
///网络请求对象
Dio dio = new Dio();
Response response;
// 判断当前的请求类型
try {
print("param");
print(this.parameters);
response = await dio.request(url, data: this.parameters, options: option);
} on DioError catch (e) {
// 请求错误处理
Response errorResponse;
if (e.response != null) {
errorResponse = e.response;
} else {
errorResponse = new Response(statusCode: 666);
}
if (e.type == DioErrorType.CONNECT_TIMEOUT) {
errorResponse.statusCode = ErrorCode.NETWORK_TIMEOUT;
}
print('请求异常: ' + e.toString());
print('请求异常 url: ' + url);
return new ResultData(errorResponse.statusCode, e.message, response.data);
}
// 对得到的结果进行解析
try {
if (option.contentType != null && option.contentType.primaryType == "text") {
print("1");
return new ResultData(response.statusCode, ErrorCode.errorDescriptionWithCode(response.statusCode), response.data);
} else {
print("2");
var responseJson = response.data;
if (response.statusCode == 201 && responseJson["token"] != null) {
// 这里需要认证
}
}
if (response.statusCode == 200 || response.statusCode == 201) {
print("3");
T result = EntityFactory.generateOBJ<T>(response.data);
if (result != null) {
print("4");
print(result);
print(T.toString());
this.result = ResultData<T>(ErrorCode.SUCCESS, ErrorCodeDescription.NETWORK_SUCCESS, result);
return new ResultData<T>(ErrorCode.SUCCESS, ErrorCodeDescription.NETWORK_SUCCESS, result);
}
else {
return new ResultData(ErrorCode.NETWORK_JSON_EXCEPTION, ErrorCodeDescription.NETWORK_JSON_EXCEPTION, response.data);
}
}
} catch (e) {
print(e.toString() + url);
return ResultData(response.statusCode, ErrorCode.errorDescriptionWithCode(response.statusCode), response.data);
}
}
}
网络请求对象是集成的对象,自身可以访问的属性包含
- baseURL
- path
- method
- parameters
- headers
- result
在这个通道中,可以感知当前网络的所有对象的现状,对于post,get,put之类的网络请求,就可以在请求发起之前做好预先的判断,准备好对应的资源,发起请求。
同时,为了更好的使用网络请求回调的结果,规定了网络回调的结构,和对应错误编码的解析
///网络错误对应的描述
class ErrorCodeDescription {
static const NETWORK_ERROR = "网络错误";
static const NETWORK_TIMEOUT = "请求超时";
static const NETWORK_JSON_EXCEPTION = "数据解析出错";
static const NETWORK_NOT_REACHABLE = "没有网络";
static const NETWORK_SUCCESS = "";
static const NETWORK_UNDEFINED_ERROR = "未知错误";
}
///网络请求错误编码
class ErrorCode {
///网络错误
static const NETWORK_ERROR = -1;
///网络超时
static const NETWORK_TIMEOUT = -2;
///网络返回数据格式化一次
static const NETWORK_JSON_EXCEPTION = -3;
static const NETWORK_NOT_REACHABLE = -4;
static const SUCCESS = 0;
static errorDescriptionWithCode(int code) {
switch (code) {
case NETWORK_ERROR: return ErrorCodeDescription.NETWORK_ERROR;
case NETWORK_TIMEOUT: return ErrorCodeDescription.NETWORK_TIMEOUT;
case NETWORK_JSON_EXCEPTION: return ErrorCodeDescription.NETWORK_JSON_EXCEPTION;
case NETWORK_NOT_REACHABLE: return ErrorCodeDescription.NETWORK_NOT_REACHABLE;
case SUCCESS: return ErrorCodeDescription.NETWORK_ERROR;
default: return ErrorCodeDescription.NETWORK_UNDEFINED_ERROR;
}
}
}
错误编码可以在上面的文件中追加,增加错误符号的可读性。对于结果结构的封装,使用的是如下对象的结构
/**
* 网络结果数据
*/
class ResultData<T> {
int status;
var msg;
T data;
ResultData(this.status, this.msg, this.data);
}
使用结果时,可以先检测网络的status和msg,对于网络json回调结果采用了泛型的承接,DIO框架为我们提供了json转model的能力,在请求实例的时候,可以指定对应的结果类型,便于在业务逻辑中取值。这一步操作,可以在NetworlPipe中看到
网络框架到这里就已经做完了,除了请求通道本身,需要在不同的业务线做丰富。业务在使用的时候,只需在默认参数的基础上添加此实例对象的差异即可,例如
class RentListInstance extends NetworkPipe {
@override
// TODO: implement path
String get path => "/home/business1";
@override
// TODO: implement method
String get method => HttpMethod.GET;
RentListInstance();
}
改用面向协议的网络结构之后,带来的好处
- 细化到最后的网络实例对象更加聚合
- 每个网络节点可读性更高
- 每个网络节点逻辑聚合更高
下面是用一个逻辑图做的网络的总结