Retrofit之OKHttpCall源码分析

之前在Retrofit源码初探一文中我们提出了三个问题:

  1. 什么时候开始将注解中参数拼装成http请求的信息的?
  2. 如何产生发起http请求对象的?
  3. 如何将对象转换成我们在接口中指定的返回值的?

其中第一个问题前几篇文章已经做了解答,今天我们探究下第二个问题。

之前也分析过,具体生成这个请求对象的是这句代码:

OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);

代码很简单,那我们就来探究下这个OkHttpCall能干什么:

final class OkHttpCall<T> implements Call<T> {

可以看到其实主要实现了一个接口,所以我们看下这个接口都有哪些方法:

public interface Call<T> extends Cloneable {
 Response<T> execute() throws IOException;
 void enqueue(Callback<T> callback);
 boolean isExecuted();
 void cancel();
 boolean isCanceled();
 Call<T> clone();
 /** The original HTTP request. */
 Request request();
}

看到这几个方法有没有很熟悉,没错,几乎和Okhttp的Call方法一模一样,我们看下okhttp的call接口:

public interface Call extends Cloneable {
 Request request();

 Response execute() throws IOException;

 void enqueue(Callback var1);

 void cancel();

 boolean isExecuted();

 boolean isCanceled();

 Call clone();

 public interface Factory {
  Call newCall(Request var1);
 }
}

从这里我们猜测,Retrofit的OkHttpCall其实就是对OkHttp的call的一种包装,下面我们详细探究下每种方法,看是如何分别调用OkHttp的call中的方法的,有没有做什么特殊处理。

之前有提过看源码之前要带着问题去看,那么对于这个OkHttpCall我们想知道什么?之前提到过这是对OkHttp的okhttp3.Call的一个封装,那么每个方法必然会调用到okhttp3.Call对应的方法,所以我们提出两个问题:

  1. 这个类中okhttp3.Call对象是怎么生成的?
  2. 调用okhttp3.Call中对应的方法时有没有做什么特殊操作?

这两个问题在每个主要方法中都能得到答案。

request()方法

 @Override public synchronized Request request() {
 okhttp3.Call call = rawCall;
 if (call != null) {
  return call.request();
 }
 if (creationFailure != null) {
  if (creationFailure instanceof IOException) {
  throw new RuntimeException("Unable to create request.", creationFailure);
  } else if (creationFailure instanceof RuntimeException) {
  throw (RuntimeException) creationFailure;
  } else {
  throw (Error) creationFailure;
  }
 }
 try {
  return (rawCall = createRawCall()).request();
 } catch (RuntimeException | Error e) {
  throwIfFatal(e); // Do not assign a fatal error to creationFailure.
  creationFailure = e;
  throw e;
 } catch (IOException e) {
  creationFailure = e;
  throw new RuntimeException("Unable to create request.", e);
 }
 }

可以看到,大致逻辑就是如果okhttp3.Call已经被实例化了直接调用它的request()方法,如果没有的话,会调用createRawCall()方法先实例化,然后再调用request方法。

所以想要解答okhttp3.Call是怎么生成的,就来看看这个createRawCall()方法:

 private okhttp3.Call createRawCall() throws IOException {
 okhttp3.Call call = serviceMethod.toCall(args);
 if (call == null) {
  throw new NullPointerException("Call.Factory returned null.");
 }
 return call;
 }

可以看到核心方法还是ServiceMethod中的toCall方法来生成的,这里提供了参数而已,继续跟进去:

okhttp3.Call toCall(@Nullable Object... args) throws IOException {
 RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
  contentType, hasBody, isFormEncoded, isMultipart);

 @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
 ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

 int argumentCount = args != null ? args.length : 0;
 if (argumentCount != handlers.length) {
  throw new IllegalArgumentException("Argument count (" + argumentCount
   + ") doesn't match expected count (" + handlers.length + ")");
 }

 for (int p = 0; p < argumentCount; p++) {
  handlers[p].apply(requestBuilder, args[p]);
 }

 return callFactory.newCall(requestBuilder.build());
 }

这个方法最终其实就是调用okhttp3.Call中的这个方法:

 public interface Factory {
  Call newCall(Request var1);
 }

至于怎么根据Request生成Call是OkHttp干的,在ServiceMethod中的toCall方法,我们要做的就是用已有信息生成一个OkHttp的Request来,如何生成这个Request?这里利用了一个RequesetBuilder。

第一:处理方法级别的注解的信息

利用httpMethod,baseUrl,relativeUrl等直接new了一个RequestBuilder出来,这些信息都是从方法级别的注解中解析出来的。
第二:处理参数级别的注解信息

之前在生成ServiceMethod对象时,利用参数级别的注解生成了一个ParameterHandler数组,每个Handler都有一个apply方法,将参数信息设置到一个RequestBuilder中,这个apply方法就是在这里调用的。

经过上面两部,一个包含了http请求完整信息的RequesetBuilder就生成了,最后build下生成一个Request传到newCall方法中,则一个okhttp3.Call对象就生成了。

整个request()方法分析完了,做的事很简单,有okhttp3.Call对象就直接调用它的request()方法,没有就生成一个再调用,但大家注意到没有,他的代码设计安排很奇怪。如果是我来写这个方法,我可能会这样写:

 public synchronized Request request1() {
 okhttp3.Call call = rawCall;
 if (call != null) {
  return call.request();
 }else{
  try {
  return (rawCall = createRawCall()).request();
  } catch (RuntimeException | Error e) {
  throwIfFatal(e); // Do not assign a fatal error to creationFailure.
  throw e;
  } catch (IOException e) {
  throw new RuntimeException("Unable to create request.", e);
  }
 }
 }

可以看到,和我自己的代码相比,原代码多了一个记录createRawCall()的异常的成员变量,这是处于效率考虑。由于我们的okhtt3.Call对象是延迟加载的,就是说在调用request方法时,其他的方法中有可能已经调用过createRawCall()方法,并由于某种原因失败了,我们将这个失败的异常记录下来,在调用createRawCall()方法之前做一次判断,如果已有异常就不需要调用createRawCall()方法了,提高了效率。

enque()

整个enque()方法的核心必然是调用okhttp3.Call的enque方法,我们重点关注调用之前有做什么,调用之后做了什么:

call.enqueue(new okhttp3.Callback() {
  @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
   throws IOException {
  Response<T> response;
  try {
   response = parseResponse(rawResponse);
  } catch (Throwable e) {
   callFailure(e);
   return;
  }
  callSuccess(response);
  }

  @Override public void onFailure(okhttp3.Call call, IOException e) {
  callFailure(e);
  }

在调用之前其实没做什么,和request()方法差不多,做了下提前判断而已,所以这里可以直接看代码,核心就是调用了parseResponse()方法将返回值转成了Retrofit的Response对象,然后调用了callSuccess()而已,所以我们跟进去:

 Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
 ResponseBody rawBody = rawResponse.body();

 // Remove the body's source (the only stateful object) so we can pass the response along.
 rawResponse = rawResponse.newBuilder()
  .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
  .build();

 int code = rawResponse.code();
 if (code < 200 || code >= 300) {
  try {
  // Buffer the entire body to avoid future I/O.
  ResponseBody bufferedBody = Utils.buffer(rawBody);
  return Response.error(bufferedBody, rawResponse);
  } finally {
  rawBody.close();
  }
 }

 if (code == 204 || code == 205) {
  rawBody.close();
  return Response.success(null, rawResponse);
 }

 ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
 try {
  T body = serviceMethod.toResponse(catchingBody);
  return Response.success(body, rawResponse);
 } catch (RuntimeException e) {
  // If the underlying source threw an exception, propagate that rather than indicating it was
  // a runtime exception.
  catchingBody.throwIfCaught();
  throw e;
 }
 }

这里逻辑很简单,根据不同的http状态码返回对应的Response对象,这里有一点,当状态码正常时,这里会利用一个converter将Body对象转成自己想要的,比如转成json等,具体处理是在serviceMethod.toResponse()中进行的。

日常偷懒环节

好了,关键时刻来了,分析了这两个方法后,OkHttpCall中的主要方法应该都讲到了,剩下的一些方法基本和上面两个差不多,大家对着来就行了!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Rxjava+Retrofit+MVP实现购物车功能

    本文实例为大家分享了Rxjava Retrofit实现购物车的具体代码,供大家参考,具体内容如下 效果图: 1.依赖 annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' compile 'com.jakewharton:butterknife:8.8.1' compile 'com.android.support:recyclerview-v7:26.0.0-alpha1' compile 'com.squareup.

  • Retrofit+RxJava实现带进度下载文件

    Retrofit+RxJava已经是目前市场上最主流的网络框架,使用它进行平常的网络请求异常轻松,之前也用Retrofit做过上传文件和下载文件,但发现:使用Retrofit做下载默认是不支持进度回调的,但产品大大要求下载文件时显示下载进度,那就不得不深究下了. 接下来我们一起封装,使用Retrofit+RxJava实现带进度下载文件. github:JsDownload 先来看看UML图: 大家可能还不太清楚具体是怎么处理的,别急,我们一步步来: 1.添依赖是必须的啦 compile 'io.

  • 详解Retrofit2.0 公共参数(固定参数)

    本文主要介绍了Retrofit2.0 公共参数(固定参数),分享给大家,具体如下: 请先阅读: Retrofit 动态参数(非固定参数.非必须参数)(Get.Post请求) 在实际项目中,对于有需要统一进行公共参数添加的网络请求,可以使用下面的代码来实现: RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(ctx).setRequestInterceptor(new RequestInterceptor() { @O

  • Okhttp、Retrofit进度获取的方法(一行代码搞定)

    起因 对于广大Android开发者来说,最近用的最多的网络库,莫过于Okhttp啦(Retrofit依赖Okhttp). Okhttp不像SDK内置的HttpUrlConnection一样,可以明确的获取数据读写的过程,我们需要执行一些操作. 介绍 Retrofit依赖Okhttp.Okhttp依赖于Okio.那么Okio又是什么鬼?别急,看官方介绍: Okio is a library that complements java.io and java.nio to make it much

  • 基于Retrofit+Rxjava实现带进度显示的下载文件

    本文实例为大家分享了Retrofit Rxjava实现下载文件的具体代码,供大家参考,具体内容如下 本文采用 :retrofit + rxjava 1.引入: //rxJava compile 'io.reactivex:rxjava:latest.release' compile 'io.reactivex:rxandroid:latest.release' //network - squareup compile 'com.squareup.retrofit2:retrofit:latest

  • RxJava+Retrofit+Mvp实现购物车

    本文实例为大家分享了RxJava Retrofit实现购物车展示的具体代码,供大家参考,具体内容如下 先给大家展示一下效果图 框架结构: 1.项目框架:MVP,图片加载用Fresco,网络请求用OKhttp+Retrofit实现(自己封装,加单例模式), 2.完成购物车数据添加(如果接口无数据,可用接口工具添加数据), 3.自定义view实现加减按钮,每次点击加减,item中的总数及总价要做出相应的改变. 4.当数量为1时,点击减号,数量不变,吐司提示用户最小数量为1. 5.底部总数及总价为所有

  • 详解Retrofit 动态参数(非固定参数、非必须参数)(Get、Post请求)

    详解Retrofit 动态参数(非固定参数.非必须参数)(Get.Post请求) 关键词:Retrofit 动态参数.非固定参数.非必须参数 有如下场景: 请求数据时: 1. 用户未登录时,不带参数userId: 2. 登录时带上参数userId. 如下接口: @GET("index.php?r=default/homepage") Observable<Response<Exercise>> getDataList(@Query("page"

  • 基于Retrofit2+RxJava2实现Android App自动更新

    本文实例为大家分享了Retrofit2 RxJava2实现Android App自动更新,具体内容如下 功能解析 自动更新可以说已经是App的标配了,很多第三方平台也都支持这个功能,最近手头上的项目需要加入这个App自动更新,考虑到项目里有用到Retrofit2和RxJava2,于是打算使用它俩自己实现这个功能. 分析App自动更新,可以分为以下三个功能点: 1.APK文件的下载 2.下载进度的实时更新显示 3.下载完成后的自动安装 其中比较难的一点是下载进度的实时更新显示,更难的是如何优雅的进

  • Retrofit 源码分析初探

    现如今,Android开发中,网络层Retrofit+Okhttp组合好像已成标配,身为技术人员,这么火的框架当然得一探究竟,不为装逼,纯粹是为了充电而已. 基本使用介绍 介绍源码前,我们先看下Retrofit的基本使用,大致了解下流程,跟着这个流程来分析源码才不会乱. 1.初始化Retrofit对象 Retrofit retrofit = new Retrofit.Builder() //使用自定义的mGsonConverterFactory .addConverterFactory(Gson

  • 详解Retrofit Interceptor(拦截器) 拦截请求并做相关处理

    本文介绍Retrofit拦截器(Interceptor)的使用方法及相关注意事项.如果本文对您有所帮助,烦请点亮小红心- 首先看一下Interceptor源码: /** * Observes, modifies, and potentially short-circuits requests going out and the corresponding * responses coming back in. Typically interceptors add, remove, or tran

随机推荐