前言
网上有很多,比如说“Flutter Dio 亲妈级别封装教程”这篇文章,该文章上有几点问题:
- 重试机制代码错误
- token存取耦合很高
- 网络请求只能针对单一地址进行访问
- 网络请求缓存机制也不是很完美。
一旦依照这样的封装去做,那么项目后期的扩展性和易用性会有一定的阻碍,那么如何做到token存取无耦合,而且还能让app多种网络地址一同请求,还可以做到针对不同请求不同超时时长处理,网络缓存还加入可自动清理的lru算法呢?那么今天这篇文章为你揭晓企业级flutter dio网络层封装。
搭建前夕准备
三方库:
dio_cache_interceptor lru缓存库 dio 网络库 retrofit 网络生成库 connectivity_plus 网络情况判断
技能:
单例模式 享元模式 迭代
文章:
持久化:跟我学企业级flutter项目:dio网络框架增加公共请求参数&header
准备好如上技能,我们来封装一套优秀的网络层
一、准备好几个基本拦截器
1、超时拦截器
import 'dart:collection';
import 'package:dio/dio.dart';
import 'package:flutter_base_lib/src/app/contants.dart';
import 'package:flutter_base_lib/src/tools/net/cache_object.dart';
class TimeInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
Map<String, dynamic> extra = options.extra;
bool connect = extra.containsKey(SysConfig.connectTimeout);
bool receive = extra.containsKey(SysConfig.receiveTimeOut);
if(connect||receive){
if(connect){
int connectTimeout = options.extra[SysConfig.connectTimeout];
options.connectTimeout = connectTimeout;
}
if(receive){
int receiveTimeOut = options.extra[SysConfig.receiveTimeOut];
options.receiveTimeout = receiveTimeOut;
}
}
super.onRequest(options, handler);
}
}
作用:单独针对个别接口进行超时时长设定,如(下载,长链接接口)
2、缓存拦截器
dio_cache_interceptor 这个库中有lru算法缓存拦截库,可直接集成
3、持久化拦截器
跟我学企业级flutter项目:dio网络框架增加公共请求参数&header 本篇文章介绍了如何持久化
4、重试拦截器
import 'dart:async';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:flutter_base_lib/src/app/application.dart';
import 'package:flutter_base_lib/src/app/contants.dart';
import 'package:flutter_ulog/flutter_ulog.dart';
import '../dio_utli.dart';
/// 重试拦截器
class RetryOnConnectionChangeInterceptor extends Interceptor {
Dio? dio;
RequestInterceptorHandler? mHandler;
// RetryOnConnectionChangeInterceptor(){
//
// }
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
mHandler = handler;
super.onRequest(options, handler);
}
@override
Future onError(DioError err, ErrorInterceptorHandler handler) async{
if (dio!=null&&Application.config.httpConfig.retry&&await _shouldRetry(err)) {
return await retryLoop(err,handler,1);
}
return super.onError(err, handler);
}
Future retryLoop(DioError err, ErrorInterceptorHandler handler,int retry) async {
try {
ULog.d("${err.requestOptions.uri.toString()} retry : ${retry}",tag: "${SysConfig.libNetTag}Retry");
await retryHttp(err,handler);
} on DioError catch (err) {
if(await _shouldRetry(err)&&retry<Application.config.httpConfig.retryCount){
await retryLoop(err,handler,retry+1);
}else{
return super.onError(err, handler);
}
}
}
Future retryHttp(DioError err, ErrorInterceptorHandler handler) async {
RequestOptions requestOptions = err.requestOptions;
Options options = Options(
method: requestOptions.method,
sendTimeout: requestOptions.sendTimeout,
receiveTimeout: requestOptions.receiveTimeout,
extra: requestOptions.extra,
headers: requestOptions.headers,
responseType: requestOptions.responseType,
contentType: requestOptions.contentType,
validateStatus: requestOptions.validateStatus,
receiveDataWhenStatusError: requestOptions.receiveDataWhenStatusError,
followRedirects: requestOptions.followRedirects,
maxRedirects: requestOptions.maxRedirects,
requestEncoder: requestOptions.requestEncoder,
responseDecoder: requestOptions.responseDecoder,
listFormat: requestOptions.listFormat,
);
var res = await dio?.request(
requestOptions.path,
cancelToken: requestOptions.cancelToken,
data: requestOptions.data,
onReceiveProgress: requestOptions.onReceiveProgress,
onSendProgress: requestOptions.onSendProgress,
queryParameters: requestOptions.queryParameters,
options: options,
);
return handler.resolve(res!);
}
///要重试的类型
Future<bool> _shouldRetry(DioError err) async{
return err.error != null && err.error is SocketException && await isConnected();
}
Future<bool> isConnected() async {
var connectivityResult = await (Connectivity().checkConnectivity());
return connectivityResult != ConnectivityResult.none;
}
}
该重试拦截器与其他文章封装不同,主要是用重试次数来管理重试机制。
5、日志拦截器
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter_base_lib/src/app/contants.dart';
import 'package:flutter_ulog/flutter_ulog.dart';
typedef void LibLogPrint(String message);
class LibLogInterceptor extends Interceptor {
LibLogInterceptor({
this.request = true,
this.requestHeader = true,
this.requestBody = false,
this.responseHeader = true,
this.responseBody = false,
this.error = true
});
/// Print request [Options]
bool request;
/// Print request header [Options.headers]
bool requestHeader;
/// Print request data [Options.data]
bool requestBody;
/// Print [Response.data]
bool responseBody;
/// Print [Response.headers]
bool responseHeader;
/// Print error message
bool error;
@override
void onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
var builder = StringBuffer('*** Request *** \n');
builder.write(_printKV('uri', options.uri));
//options.headers;
if (request) {
builder.write(_printKV('method', options.method));
builder.write(_printKV('responseType', options.responseType.toString()));
builder.write(_printKV('followRedirects', options.followRedirects));
builder.write(_printKV('connectTimeout', options.connectTimeout));
builder.write(_printKV('sendTimeout', options.sendTimeout));
builder.write(_printKV('receiveTimeout', options.receiveTimeout));
builder.write(_printKV(
'receiveDataWhenStatusError', options.receiveDataWhenStatusError));
builder.write(_printKV('extra', options.extra));
}
if (requestHeader) {
builder.write('headers:\n');
options.headers.forEach((key, v) => builder.write(_printKV(' $key', v)));
}
if (requestBody) {
var res = options.data;
builder.write('data:\n');
builder.write(_message(res));
// try{
// ULog.json(res.toString(),tag: "${SysConfig.libNetTag}RequestJson");
// } on Exception catch (e) {
// ULog.d(res,tag: "${SysConfig.libNetTag}RequestJson");
// }
}
ULog.d(builder.toString(),tag: "${SysConfig.libNetTag}Request");
handler.next(options);
}
// Handles any object that is causing JsonEncoder() problems
Object toEncodableFallback(dynamic object) {
return object.toString();
}
String _message(dynamic res) {
if (res is Map || res is Iterable) {
var encoder = JsonEncoder.withIndent(' ', toEncodableFallback);
return encoder.convert(res);
} else {
return res.toString();
}
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) async {
var builder = StringBuffer('*** Response *** \n');
_printResponse(response,builder,(message){
ULog.d(message,tag: "${SysConfig.libNetTag}Response");
});
handler.next(response);
}
@override
void onError(DioError err, ErrorInterceptorHandler handler) async {
if (error) {
var builder = StringBuffer('*** DioError *** \n');
builder.write('uri: ${err.requestOptions.uri}\n');
builder.write('$err');
if (err.response != null) {
_printResponse(err.response!,builder,(message){
ULog.e(message,tag: "${SysConfig.libNetTag}Error");
});
}else{
ULog.e(builder.toString(),tag: "${SysConfig.libNetTag}Error");
}
}
handler.next(err);
}
void _printResponse(Response response,StringBuffer builder,LibLogPrint pr) {
builder.write(_printKV('uri', response.requestOptions.uri));
if (responseHeader) {
builder.write(_printKV('statusCode', response.statusCode));
if (response.isRedirect == true) {
builder.write(_printKV('redirect', response.realUri));
}
builder.write('headers:\n');
response.headers.forEach((key, v) => builder.write(_printKV(' $key', v.join('\r\n\t'))));
}
if (responseBody) {
var res = response.toString();
builder.write('Response Text:\r\n');
var resJ = res.trim();
if (resJ.startsWith("{")) {
Map<String, dynamic> decode = JsonCodec().decode(resJ);
builder.write(_message(decode));
}else if (resJ.startsWith("[")) {
List decode = JsonCodec().decode(resJ);
builder.write(_message(decode));
}else {
builder.write(res);
}
// try{
// ULog.json(res,tag: "${SysConfig.libNetTag}ResponseJson");
// } on Exception catch (e) {
// ULog.d(res,tag: "${SysConfig.libNetTag}ResponseJson");
// }
}
pr(builder.toString());
}
String _printKV(String key, Object? v) {
return '$key: $v \n';
}
}
主要是日志拦截打印
6、错误拦截器
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter_base_lib/src/app/contants.dart';
import 'package:flutter_base_lib/src/exception/lib_network_exception.dart';
import 'package:flutter_base_lib/src/tools/net/dio_utli.dart';
import 'package:flutter_ulog/flutter_ulog.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_base_lib/src/lib_localizations.dart';
/// 错误处理拦截器
class ErrorInterceptor extends Interceptor {
// 是否有网
Future<bool> isConnected() async {
var connectivityResult = await (Connectivity().checkConnectivity());
return connectivityResult != ConnectivityResult.none;
}
@override
Future<void> onError(DioError err, ErrorInterceptorHandler handler) async {
if (err.type == DioErrorType.other) {
bool isConnectNetWork = await isConnected();
if (!isConnectNetWork && err.error is SocketException) {
err.error = SocketException(LibLocalizations.getLibString().libNetWorkNoConnect!);
}else if (err.error is SocketException){
err.error = SocketException(LibLocalizations.getLibString().libNetWorkError!);
}
}
err.error = LibNetWorkException.create(err);
ULog.d('DioError : ${err.error.toString()}',tag: "${SysConfig.libNetTag}Interceptor");
super.onError(err, handler);
}
}
与其他人封装不同,服务器请求异常code,我将其抛到业务层自主处理。常规异常则走库文案。
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter_base_lib/src/lib_localizations.dart';
class LibNetWorkException implements Exception{
final String _message;
final int _code;
int get code{
return _code;
}
String get message{
return _message;
}
LibNetWorkException( this._code,this._message);
@override
String toString() {
return "$_code : $_message";
}
factory LibNetWorkException.create(DioError error) {
switch (error.type) {
case DioErrorType.cancel:{
return LibNetWorkException(-1, LibLocalizations.getLibString().libNetRequestCancel!);
}
case DioErrorType.connectTimeout:{
return LibNetWorkException(-1, LibLocalizations.getLibString().libNetFailCheck!);
}
case DioErrorType.sendTimeout:{
return LibNetWorkException(-1, LibLocalizations.getLibString().libNetTimeOutCheck!);
}
case DioErrorType.receiveTimeout:{
return LibNetWorkException(-1, LibLocalizations.getLibString().libNetResponseTimeOut!);
}
case DioErrorType.response:{
try{
return LibNetWorkException(error.response!.statusCode!,"HTTP ${error.response!.statusCode!}:${LibLocalizations.getLibString().libNetServerError!}");
} on Exception catch (_) {
return LibNetWorkException(-1, error.error.message);
}
}
default:
{
return LibNetWorkException(-1, error.error.message);
}
}
}
}
二、工具类封装
1、主要类
import 'dart:io';
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:flutter_base_lib/src/tools/net/interceptor/error_interceptor.dart';
import 'package:flutter_base_lib/src/tools/net/interceptor/lib_log_interceptor.dart';
import '../../../flutter_base_lib.dart';
import 'interceptor/presistent_interceptor.dart';
import 'interceptor/retry_on_connection_change_interceptor.dart';
import 'interceptor/time_interceptor.dart';
class DioUtil{
final String _baseUrl;
final HttpConfig _config;
final List<Interceptor> _interceptors;
late Dio _dio;
Dio get dio{
return _dio;
}
DioUtil._internal(this._baseUrl, this._config, this._interceptors){
BaseOptions options = new BaseOptions(
baseUrl: _baseUrl,
connectTimeout: _config.connectTimeout,
receiveTimeout: _config.receiveTimeOut,
);
_dio = new Dio(options);
var retry = new Dio(options);
_interceptors.forEach((element) {
if(element is RetryOnConnectionChangeInterceptor){
element.dio = retry;
}else{
if(!(element is ErrorInterceptor)){
retry.interceptors.add(element);
}
}
_dio.interceptors.add(element);
});
proxy(_dio);
proxy(retry);
}
void proxy(Dio dio){
if (SpSotre.instance.getBool(SysConfig.PROXY_ENABLE)??false) {
String? porxy = SpSotre.instance.getString(SysConfig.PROXY_IP_PROT)??null;
if(porxy!=null){
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
client.findProxy = (uri) {
return "PROXY $porxy";
};
//代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
};
}
}
}
static late Map<String,DioUtil> _dioUtils = Map();
static DioUtil instance(String baseUrl,{HttpConfig? config, List<Interceptor>? interceptors,List<Interceptor>? applyInterceptors}){
if(!_dioUtils.containsKey(baseUrl)){
List<Interceptor> list = [PresistentInterceptor(),TimeInterceptor(),RetryOnConnectionChangeInterceptor(),LibLogInterceptor(requestBody: Application.config.debugState,responseBody: Application.config.debugState),ErrorInterceptor()];
// List<Interceptor> list = [ErrorInterceptor(),PresistentInterceptor()];
var inter = interceptors??list;
if(applyInterceptors!=null){
inter.addAll(applyInterceptors);
}
_dioUtils[baseUrl] = DioUtil._internal(baseUrl,config??Application.config.httpConfig,inter);
}
return _dioUtils[baseUrl]!;
}
// CancelToken _cancelToken = new CancelToken();
}
工具类封装,主要运用享元模式,可以支持多种url进行访问,不同的url有不同的配置。(灵活可用)
2、辅助类:
class HttpConfig{
final int _connectTimeout ;
final int _receiveTimeOut ;
final bool _retry;
final int _retryCount;
get connectTimeout{
return _connectTimeout;
}
get receiveTimeOut{
return _receiveTimeOut;
}
get retry{
return _retry;
}
get retryCount{
return _retryCount;
}
HttpConfig(HttpConfigBuilder builder): _connectTimeout = builder._connectTimeout,_receiveTimeOut = builder._receiveTimeOut,_retry = builder._retry,_retryCount = builder._retryCount;
}
class HttpConfigBuilder {
int _connectTimeout = 10000;//连接超时时间
int _receiveTimeOut = 30000;//接收超时时间
bool _retry = false;
int _retryCount = 3;
// var maxRetry = 1 重试次数
HttpConfigBuilder setConnectTimeout(int connectTimeout){
_connectTimeout = connectTimeout;
return this;
}
HttpConfigBuilder setReceiveTimeOut(int receiveTimeOut){
_receiveTimeOut = receiveTimeOut;
return this;
}
HttpConfigBuilder setRetry(bool retry){
_retry = retry;
return this;
}
HttpConfigBuilder setRetryCount(int retryCount){
_retryCount = _retryCount;
return this;
}
HttpConfig build() => HttpConfig(this);
}
三、使用
import 'package:flutter_app_me/data/model/api_result.dart';
import 'package:flutter_app_me/data/model/user.dart';
import 'package:flutter_app_me/data/model/user_infos.dart';
import 'package:flutter_base_lib/flutter_base_lib.dart';
import 'package:retrofit/retrofit.dart';
import 'package:dio/dio.dart';
import 'api_methods.dart';
part 'api_service.g.dart';
@RestApi()
abstract class RestClient {
factory RestClient(Dio dio, {String baseUrl}) = _RestClient;
@GET(ApiMethods.userinfoJson)
Future<ApiResult<UserInfos>> userinfoJson();
// "test123332","123456"
@POST(ApiMethods.login)
@Extra({SysConfig.connectTimeout:100000})
Future<ApiResult<UserInfos>> userLogin(@Queries() User user);
}
网络请求配置
class BusinessErrorException implements Exception {
final int _errorCode;
final String? _errorMsg;
BusinessErrorException(this._errorCode, this._errorMsg);
int get errorCode {
return _errorCode;
}
String? get errorMsg => _errorMsg;
}
class TokenTimeOutException implements Exception {
final String? _errorMsg;
TokenTimeOutException(this._errorMsg);
String? get errorMsg => _errorMsg;
}
class RequestCodeErrorException implements Exception {
final String? _errorMsg;
final int _errorCode;
RequestCodeErrorException(this._errorCode, this._errorMsg);
int get errorCode {
return _errorCode;
}
String? get errorMsg => _errorMsg;
}
业务基本异常
import 'package:business_package_auth/business_package_auth.dart';
import 'package:flutter_base_lib/flutter_base_lib.dart';
import 'package:flutter_base_ui/flutter_base_ui.dart';
import 'package:dio/dio.dart';
import 'package:wisdomwork_lib/src/model/api_result.dart';
const int httpSuccessCode = 0;
const int httpErrorCode = 1;
const int httpTokenExt = 10001;
extension SuccessExt<T> on Success<T> {
Success<T> appSuccess() {
var data = this.data;
if (data is ApiResult) {
if (data.code != httpSuccessCode) {
switch (data.code){
case httpTokenExt:
TipToast.instance.tip(data.msg ?? LibLocalizations.getLibString().libBussinessTokenTimeOut!,tipType: TipType.warning);
BlocProvider.of<AuthenticationBloc>(LibRouteNavigatorObserver.instance.navigator!.context).add(LogOut());
throw TokenTimeOutException(data.msg);
case httpErrorCode:
TipToast.instance.tip(data.msg ?? LibLocalizations.getLibString().libBussinessRequestCodeError!,tipType: TipType.error);
throw RequestCodeErrorException(data.code!,data.msg);
default:
throw BusinessErrorException(data.code!, data.msg);
}
}
}
return this;
}
}
extension ErrorExt<T> on Error<T> {
void appError() {
var exception = this.exception;
if (exception is LibNetWorkException) {
TipToast.instance.tip(exception.message, tipType: TipType.error);
}
}
}
typedef ResultF<T> = Future<ApiResult<T>> Function();
mixin RemoteBase {
Future<DataResult<ApiResult<T>>> remoteDataResult<T>(ResultF<T> resultF) async {
try {
var data = await resultF.call();
return Success(data).appSuccess();
} on DioError catch (err, stack) {
var e = err.error;
ULog.e(e.toString(), error: e, stackTrace: stack);
return Error(e)..appError();
} on Exception catch (e, stack) {
ULog.e(e.toString(), error: e, stackTrace: stack);
return Error(e)..appError();
}
}
}
业务基本异常处理方式
import 'package:flutter_base_lib/flutter_base_lib.dart';
import 'package:flutter_base_ui/flutter_base_ui.dart';
import 'package:wisdomwork/data/services/api_service.dart';
import 'package:wisdomwork_lib/wisdomwork_lib.dart';
mixin WisdomworkRemoteBase{
var rest = RestClient(DioUtil.instance(
AppEnvironment.envConfig![AppConfig.apiName]!,
applyInterceptors: [UiNetInterceptor()]).dio);
}
业务请求接口,实现
final data = await AppResponsitory.instance.login(state.phoneText, state.codeText);
if (userResult != null) {
if (userResult is Success) {
if (userResult.data!.data!= null) {
onGetUser(userResult.data!.data!, context);
}
} else if(userResult is Error){
var exception = (userResult as Error).exception;
if(exception is BusinessErrorException){
Fluttertoast.showToast(msg: exception.errorMsg.toString());
}
}
}
业务请求与异常处理
|