DIO源码解析

dio是一个强壮的Dart Http恳求库,支撑Restful API、FormData、阻拦器、恳求撤销、Cookie管理、文件上传/下载、超时、自界说适配器等

Dio版本号:4.0.6

根本运用

final dio = Dio();
final result = await dio.get('https://xxxx.ccc');

源码剖析

源码剖析通常情况下是一个逆推的过程,首要熟悉api的运用,然后经过api的调用考虑功能是如何完结的。这儿就从Dio()和get()办法作为切入点,看看Dio的内部完结。切忌直接下载源码通读一遍,容易找不到重点。

Dio

检查源码发现Dio是个笼统类,界说了Dio支撑的一切功能。有面向目标经验的应该都知道笼统类无法直接实例化,可是这儿却可行其实这是dart的factory语法糖,便利开发者运用工厂形式创立目标。

简化的Dio代码,例举出比较具有代表性的属性和办法。

abstract class Dio {
  factory Dio([BaseOptions? options]) => createDio(options);
  late BaseOptions options;
  Interceptors get interceptors;
  late HttpClientAdapter httpClientAdapter;
  late Transformer transformer;
  ...
  Future<Response<T>> get<T>(
    String path, {
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onReceiveProgress,
  });
  Future<Response<T>> request<T>(
    String path, {
    data,
    Map<String, dynamic>? queryParameters,
    CancelToken? cancelToken,
    Options? options,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  });
  ...
  }

1. 工厂办法创立Dio目标

factory Dio([BaseOptions? options]) => createDio(options);
这是上面说到的为何笼统类能实例化目标,便是个语法糖起了效果,跟进去发现createDio(options)这个办法界说在entry_stub.dart里,并且是个空完结。先不深究,横竖最后的完结要么是DioForBrowser、要么是DioForNative至于加了什么魔法不是本期的重点

2. BaseOptions

options保存通用的恳求信息,比如baseUrl、headers、超时时刻等参数。用来设置大局配置。

3. Interceptors

这便是一切http恳求结构里都会用到的阻拦器在Dio里的完结,里面的要害源码一个线性列表存储一切的阻拦器,和重写的下标操作符。在建议恳求时会运用Interceptors存储的阻拦器按次序进行阻拦处理。

4. HttpClientAdapter

HttpClientAdapter是Dio真实建议恳求的当地,他是一个笼统类,完结类经过依靠注入注入进来。Dio这儿运用了责任分离的思维进行接耦,Dio界说恳求办法和恳求阻拦等操作,运用HttpClientAdapter树立衔接建议恳求。这样设计的好处在于,如若对网络恳求库有改动的需求能够自己完结一个HttpClientAdapter子类进行替换就行,无需改动原有代码。

5. Transformer

Transformer的效果是在恳求前后能够对恳求参数,和恳求成果进行修正。在恳求时收效在恳求阻拦器之后,呼应时发生在呼应阻拦器之前。关于了解过洋葱模型的同学来说,这很好了解,Transformer处于Interceptors的里面一层。

6. 诸如get、post、request…办法

Dio里界说的办法全部都是笼统办法,需求子类来完结。这儿的效果是界说一个通用的恳求接口,包括http常用的一些办法。

依照程序看完笼统类就该看完结类了,Android Studio里在笼统类Dio的左边有个向下的箭头,点击一下发现有三个子类。

1. DioMixin

DioMixin也是一个笼统类,完结了Dio接口几乎一切的办法,只有两个属性未完结:

  • HttpClientAdapter

  • BaseOptions

    这两个属性交由DioForNative和DioForBrowser各自进行注入。

class DioForBrowser with DioMixin implements Dio {
  DioForBrowser([BaseOptions? options]) {
    this.options = options ?? BaseOptions();
    httpClientAdapter = BrowserHttpClientAdapter();
  }
}
class DioForNative with DioMixin implements Dio {
  singleton.
  DioForNative([BaseOptions? baseOptions]) {
    options = baseOptions ?? BaseOptions();
    httpClientAdapter = DefaultHttpClientAdapter();
  }
  }

这个很好了解,因为native和web的建议恳求肯定是不一样的。dio默认运用的http_client来自于dart_sdk暂未直接支撑web。所以需求经过创立不同的http_client适配web和native。

好了,到这儿根本确认DioMixin这个类便是Dio最重要的完结类了。DioForNative和DioForBrowser仅仅针对不同渠道的适配而已。持续剖析DioMixin:

同样从get办法开始跟进

  Future<Response<T>> get<T>(
    String path, {
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onReceiveProgress,
  }) {
    return request<T>(
      path,
      queryParameters: queryParameters,
      options: checkOptions('GET', options),
      onReceiveProgress: onReceiveProgress,
      cancelToken: cancelToken,
    );
  }

get办法里设置了一下method为‘GET’,然后把参数全数传递给了request办法,持续看看request办法

  Future<Response<T>> request<T>(
    String path, {
    data,
    Map<String, dynamic>? queryParameters,
    CancelToken? cancelToken,
    Options? options,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {
    options ??= Options();
    var requestOptions = options.compose(
      this.options,
      path,
      data: data,
      queryParameters: queryParameters,
      onReceiveProgress: onReceiveProgress,
      onSendProgress: onSendProgress,
      cancelToken: cancelToken,
    );
    requestOptions.onReceiveProgress = onReceiveProgress;
    requestOptions.onSendProgress = onSendProgress;
    requestOptions.cancelToken = cancelToken;
    ...
    return fetch<T>(requestOptions);
  }

request办法里首要干了两件事

  1. 兼并BaseOptions和外部传进来的恳求参数
  2. 绑定上传、下载、撤销等回调到恳求目标

然后将处理好的恳求参数交给fetch办法。持续跟进(前方高能,fetch办法是dio的核心了)

  Future<Response<T>> fetch<T>(RequestOptions requestOptions) async {
    final stackTrace = StackTrace.current;
    if (requestOptions.cancelToken != null) {
      requestOptions.cancelToken!.requestOptions = requestOptions;
    }
    //这儿是依据恳求参数,简略判别下回来的type。意思是T假如声明晰类型,要么是一般文本要么是json目标
    if (T != dynamic &&
        !(requestOptions.responseType == ResponseType.bytes ||
            requestOptions.responseType == ResponseType.stream)) {
      if (T == String) {
        requestOptions.responseType = ResponseType.plain;
      } else {
        requestOptions.responseType = ResponseType.json;
      }
    }
    //恳求阻拦包装器:interceptor便是阻拦器里的onRequest办法,作为参数传过来
    //1.开始剖析这个包装器的效果,仅当状态处于next时开始工作
    //2.listenCancelForAsyncTask办法效果是,cancelToken的Future和恳求的阻拦器Future一起履行,cancelToken先履行完结的话就抛出反常终止恳求。
    //3.创立一个requestHandler,并调用interceptor办法(在request这儿便是onRequest办法),然后回来requestHander.future(了解Completer的同学应该都知道,这是能够手动操控future的办法)。这就解说了为何阻拦器里的onRequest办法,开发者需求手动调用next等办法进入下一个阻拦器。
    FutureOr Function(dynamic) _requestInterceptorWrapper(
      InterceptorSendCallback interceptor,
    ) {
      return (dynamic _state) async {
        var state = _state as InterceptorState;
        if (state.type == InterceptorResultType.next) {
          return listenCancelForAsyncTask(
            requestOptions.cancelToken,
            Future(() {
              return checkIfNeedEnqueue(interceptors.requestLock, () {
                var requestHandler = RequestInterceptorHandler();
                interceptor(state.data as RequestOptions, requestHandler);
                return requestHandler.future;
              });
            }),
          );
        } else {
          return state;
        }
      };
    }
    //呼应阻拦包装器:
    //完结方式参阅_requestInterceptorWrapper根本相似,可是要注意这儿放宽了state的条件多了一个resolveCallFollowing,这个后续再讲
    FutureOr<dynamic> Function(dynamic) _responseInterceptorWrapper(
      InterceptorSuccessCallback interceptor,
    ) {
      return (_state) async {
        var state = _state as InterceptorState;
        if (state.type == InterceptorResultType.next ||
            state.type == InterceptorResultType.resolveCallFollowing) {
          return listenCancelForAsyncTask(
            requestOptions.cancelToken,
            Future(() {
              return checkIfNeedEnqueue(interceptors.responseLock, () {
                var responseHandler = ResponseInterceptorHandler();
                interceptor(state.data as Response, responseHandler);
                return responseHandler.future;
              });
            }),
          );
        } else {
          return state;
        }
      };
    }
    // 过错阻拦包装器
    FutureOr<dynamic> Function(dynamic, StackTrace) _errorInterceptorWrapper(
        InterceptorErrorCallback interceptor) {
      return (err, stackTrace) {
        if (err is! InterceptorState) {
          err = InterceptorState(
            assureDioError(
              err,
              requestOptions,
            ),
          );
        }
        if (err.type == InterceptorResultType.next ||
            err.type == InterceptorResultType.rejectCallFollowing) {
          return listenCancelForAsyncTask(
            requestOptions.cancelToken,
            Future(() {
              return checkIfNeedEnqueue(interceptors.errorLock, () {
                var errorHandler = ErrorInterceptorHandler();
                interceptor(err.data as DioError, errorHandler);
                return errorHandler.future;
              });
            }),
          );
        } else {
          throw err;
        }
      };
    }
    // Build a request flow in which the processors(interceptors)
    // execute in FIFO order.
    // Start the request flow
    // 初始化恳求阻拦器第一个元素,第一个InterceptorState的type为next
    var future = Future<dynamic>(() => InterceptorState(requestOptions));
    // Add request interceptors to request flow
    // 这是构成恳求阻拦链的要害,遍历阻拦器的onRequest办法,并且运用_requestInterceptorWrapper对onRequest办法进行包装。
    //上面讲到_requestInterceptorWrapper回来的是一个future
    //future = future.then(_requestInterceptorWrapper(fun));这段代码便是让阻拦器构成一个链表,只有上一个阻拦器里的onRequest内部调用了next()才会进入下一个阻拦器。
    interceptors.forEach((Interceptor interceptor) {
      var fun = interceptor is QueuedInterceptor
          ? interceptor._handleRequest
          : interceptor.onRequest;
      future = future.then(_requestInterceptorWrapper(fun));
    });
    // Add dispatching callback to request flow
    // 建议恳求的当地,建议恳求时也处在future链表里,便利response阻拦器和error阻拦器的处理后续。
    //1. reqOpt即,经过阻拦器处理后的终究恳求参数
    //2. _dispatchRequest履行恳求,并依据恳求成果判别履行resolve还是reject
    future = future.then(_requestInterceptorWrapper((
      RequestOptions reqOpt,
      RequestInterceptorHandler handler,
    ) {
      requestOptions = reqOpt;
      _dispatchRequest(reqOpt)
          .then((value) => handler.resolve(value, true))
          .catchError((e) {
        handler.reject(e as DioError, true);
      });
    }));
    //request处理履行完结后,进入response阻拦处理器,遍历构成response阻拦链表
    // Add response interceptors to request flow
    interceptors.forEach((Interceptor interceptor) {
      var fun = interceptor is QueuedInterceptor
          ? interceptor._handleResponse
          : interceptor.onResponse;
      future = future.then(_responseInterceptorWrapper(fun));
    });
    // 恳求阻拦链表添加完结后,添加过错的链表
    // Add error handlers to request flow
    interceptors.forEach((Interceptor interceptor) {
      var fun = interceptor is QueuedInterceptor
          ? interceptor._handleError
          : interceptor.onError;
      future = future.catchError(_errorInterceptorWrapper(fun));
    });
    // Normalize errors, we convert error to the DioError
    // 终究回来经过了阻拦器链表的成果
    return future.then<Response<T>>((data) {
      return assureResponse<T>(
        data is InterceptorState ? data.data : data,
        requestOptions,
      );
 }).catchError((err, _) {
      var isState = err is InterceptorState;
      if (isState) {
        if ((err as InterceptorState).type == InterceptorResultType.resolve) {
          return assureResponse<T>(err.data, requestOptions);
        }
      }
      throw assureDioError(
        isState ? err.data : err,
        requestOptions,
        stackTrace,
      );
    });
  }

关于fetch的源码剖析在要害点写了注释,各位同学自行享受。像这种比较长的源码剖析一直在考虑该如何写,分块解析怕代码逻辑关联不上来,索性直接全部拿来写上注释。再经过实例问题解说代码,各位假如有好的建议去剖析代码,请在谈论区留言

关于阻拦器的几个实例问题

  1. 阻拦器不手动调用RequestInterceptorHandler.next会怎么样?
    答:依据我们整理的流程来看,Dio在建议恳求时会依据阻拦器生成一个future的链表,future只有等到上一个履行完才会履行下一个。假如阻拦器里不手动调用next则会停留在链表中的某个节点。

  2. 阻拦器onError中能够做哪些操作?

interceptors.forEach((Interceptor interceptor) {
  var fun = interceptor is QueuedInterceptor
        ? interceptor._handleError
        : interceptor.onError;
    future = future.catchError(_errorInterceptorWrapper(fun));
  });

对这段代码进行剖析,能够看到onError的履行,是在future链表的catchError(捕获future里的过错)办法中进行的。

onError的办法签名如下
void onError(DioError err,ErrorInterceptorHandler handler)
能够在onError办法调用 next、resolve、reject三个办法处理。

next 运用的completeError,会让future发生一个过错,被catch到交给下一个阻拦器处理。

void next(DioError err) {
    _completer.completeError(
      InterceptorState<DioError>(err),
      err.stackTrace,
    );
    _processNextInQueue?.call();
  }  

resolve 运用complete会正常回来数据,不会触发catchError,所以越过后续的onError阻拦器

void resolve(Response response) {
    _completer.complete(InterceptorState<Response>(
      response,
      InterceptorResultType.resolve,
    ));
    _processNextInQueue?.call();
  }

reject 和next代码相似,可是设置了状态为InterceptorResultType.reject,结合_errorInterceptorWrapper代码看,包装器里只处理err.type == InterceptorResultType.next ||
err.type == InterceptorResultType.rejectCallFollowing条件,其他状态直接抛出反常。所以reject的效果便是抛出过错直接完结恳求

  void reject(DioError error) {
    _completer.completeError(
      InterceptorState<DioError>(
        error,
        InterceptorResultType.reject,
      ),
      error.stackTrace,
    );
    _processNextInQueue?.call();
  }
//error包装器
FutureOr<dynamic> Function(dynamic, StackTrace) _errorInterceptorWrapper(
        InterceptorErrorCallback interceptor) {
      return (err, stackTrace) {
        if (err is! InterceptorState) {
          err = InterceptorState(
            assureDioError(
              err,
              requestOptions,
            ),
          );
        }
//仅会处理InterceptorResultType.next和InterceptorResultType.rejectCallFollowing,而reject的类型是reject,所以直接履行elese的throw
        if (err.type == InterceptorResultType.next ||
            err.type == InterceptorResultType.rejectCallFollowing) {
          return listenCancelForAsyncTask(
            requestOptions.cancelToken,
            Future(() {
              return checkIfNeedEnqueue(interceptors.errorLock, () {
                var errorHandler = ErrorInterceptorHandler();
                interceptor(err.data as DioError, errorHandler);
                return errorHandler.future;
              });
            }),
          );
        } else {
          throw err;
        }
      };
    }
  1. 在onRequest里抛出反常,后续的onRequest和onResponse还会回调吗?
  2. 在onResponse里抛出反常,后续的onResponse还会回调吗?
答:回忆一下恳求的流程,建议恳求->onRequest(1)->onRequest(2)->onRequest(3)->http恳求->onResponse(1)->onResponse(2)->onResponse(3)->catchError(1)->catchError(2)->catchError(3)。
这就很明显无论是onRequest还是onResponse抛出反常都会被catchError(1)给捕获,越过了后续的onRequest和onResponse。

弥补

  • 在requestWrapper、responseWrapper、errorWrapper里都能够看到listenCancelForAsyncTask,第一个参数是cancelToken。这是因为Dio的撤销恳求是在阻拦器里进行的,只要恳求还未走完阻拦器就能够撤销恳求。这就有个新的问题,假如咱们未设置阻拦器撤销恳求就无法运用了吗?明显不是。在建议恳求的时分还会把cancelToken再次传递进去,监听是否需求撤销恳求,假如撤销的话就封闭衔接,感兴趣的同学自行检查相关源码。
  Future<Response<T>> _dispatchRequest<T>(RequestOptions reqOpt) async {
    var cancelToken = reqOpt.cancelToken;
    ResponseBody responseBody;
    try {
      var stream = await _transformData(reqOpt);
      responseBody = await httpClientAdapter.fetch(
        reqOpt,
        stream,
        cancelToken?.whenCancel,
      );
      ...
      }
  • InterceptorResultType在阻拦器里发挥的效果,上面也提过了其实便是在调用InterceptorHandler的next,resolve,reject时设着一个标记,用于判别是持续下一个阻拦器还是越过后续阻拦办法

2. DioForNative 和 DioForBrowser

下面这段代码是DioMixin建议恳求的当地,能够看到真实履行http恳求的是HttpClientAdapter的fetch办法。那DioForNative和DioForBrowser是针对不同渠道的完结,那最简略的办法便是对fetch办法进行定制就好了。上面也说到了他们各自创立了不同的HttpClientAdapter,感兴趣的同学能够看看BrowserHttpClientAdapter和DefaultHttpClientAdapter

 Future<Response<T>> _dispatchRequest<T>(RequestOptions reqOpt) async {
    var cancelToken = reqOpt.cancelToken;
    ResponseBody responseBody;
    try {
      var stream = await _transformData(reqOpt);
      responseBody = await httpClientAdapter.fetch(
        reqOpt,
        stream,
        cancelToken?.whenCancel,
      );
      ...
      }

总结

Dio也是一个网络封装库,本身并不负责树立http恳求等操作。除此之外还集成了恳求阻拦,撤销恳求的功能。采用了面向接口的方式,所以替换http恳求库价值很小,只需求自己完结HttpClientAdapter替换下即可。