likes
comments
collection
share

DIO源码浅析——揭开面纱后是你印象中的样子吗

作者站长头像
站长
· 阅读数 36

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替换下即可。

转载自:https://juejin.cn/post/7202793493826781241
评论
请登录