OkHttp源码简析

Android平台有很多优秀的开源库,OkHttp绝对是其中的佼佼者,它是Square出品的一个网络通讯库,功能强大、稳定可靠,以至于Google也在4.4以后用OkHttp来实现HttpURLConnectiond的底层功能,本文将通过阅读项目的源码,一步步构建OkHttp的项目架构,了解这个强大的通讯库是如何设计的。

简单用例

我们先看看OkHttp的基本使用方法
Get请求:

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}

Post请求:

public static final MediaType JSON
    = MediaType.parse("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(JSON, json);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  Response response = client.newCall(request).execute();
  return response.body().string();
}

发送请求需要首先构建一个担当客户端角色的OkHttpClient,再构建我们要发送的请求Request,填充好url和body后,通过OkHttpClient把Request发送出去,获取并返回Response,这个流程如下图:

OkHttp还支持异步请求,异步请求的使用方式:

client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        System.out.println(response.body().string());
    }
});

异步请求会通过Callback来监听异步请求结果,加上异步请求的流程图如下:

以上是OkHttp的基本用例,接下来我们要进入到源码来分析它究竟是怎么工作的。

构建OkHttpClient

OkHttpClient可以使用Builder来配置超时、代理、DNS等各种参数,之后调用builder()来生成OkHttpClient对象。

OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new LoggingInterceptor())
    ...... //各种配置
    .build();

查看源码可以看到,OkHttpClient的无参构造方法实际上也是通过调用Builder方法来构建,只是省略了配置参数的过程。

public OkHttpClient() {
  this(new Builder());
}

之后,Builder的各个配置会保存在Client中,供后面的操作来读取使用。加上Builder的流程图如下:

发送请求

无论是同步还是异步请求,都需要先调用newCall方法获取Call对象,而Call对象是什么呢?

public interface Call extends Cloneable {

  Request request();

  Response execute() throws IOException;

  void enqueue(Callback responseCallback);

  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}

Call是一个已经准备好执行的请求,值得注意的是,它是工厂模式的一个产物,OkHttpClient就是一个继承了Call.Factory接口的工厂类。

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {

  ...

  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false);
  }

  ...

}

在OkHttpClient的newCall方法中,创建的是一个RealCall对象,RealCall才是实现核心通讯功能的主角。
我们先看RealCall的execute同步执行方法。

  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }

在执行的时候,RealCall先检查自身是否已经执行过,然后执行eventListener的回调并在dispatcher中通知client自己的状态改变,关键的发送请求操作是在getResponseWithInterceptorChain方法中实现的。
再看看RealCall的enqueue异步执行方法。

  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

final class AsyncCall extends NamedRunnable {

  ...

  @Override protected void execute() {
    boolean signalledCallback = false;
    try {
      Response response = getResponseWithInterceptorChain();
      if (retryAndFollowUpInterceptor.isCanceled()) {
        signalledCallback = true;
        responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
      } else {
        signalledCallback = true;
        responseCallback.onResponse(RealCall.this, response);
      }
    } catch (IOException e) {
      if (signalledCallback) {
        // Do not signal the callback twice!
        Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
      } else {
        eventListener.callFailed(RealCall.this, e);
        responseCallback.onFailure(RealCall.this, e);
      }
    } finally {
      client.dispatcher().finished(this);
    }
  }

异步请求通过构建一个AsyncCall来实现,OkHttpClient的Dispatcher会在适当的时机调用AsyncCall,在AsyncCall中我们也能看到getResponseWithInterceptorChain的身影,事实上,所有请求操作都是在getResponseWithInterceptorChain中实现的,想要知道请求的发送过程,还是要看getResponseWithInterceptorChain的实现方式。

Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());
  interceptors.add(retryAndFollowUpInterceptor);
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  interceptors.add(new CacheInterceptor(client.internalCache()));
  interceptors.add(new ConnectInterceptor(client));
  if (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));
  Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
      originalRequest, this, eventListener, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());
  return chain.proceed(originalRequest);
}

getResponseWithInterceptorChain中生成了一个Interceptor的列表,并根据这个列表维护一个Interceptor链,每一个Interceptor都实现了一部分功能,发送请求就是按顺序执行这个Interceptor链的过程。

  • RetryAndFollowUpInterceptor负责失败重试和重定向请求
  • BridgeInterceptor负责桥接应用和网络间的请求和响应
  • CacheInterceptor负责管理网络缓存
  • ConnectInterceptor负责和服务器建立连接
  • CallServerInterceptor负责发起请求

除此之外,RealCall还会在合适的时机插入开发者定义的ApplicationInterceptors和NetworkInterceptor,两种Interceptor的主要差别在于是否一定会被执行和执行的次数(wiki
),从这两种Interceptor插入时机来看,我们就不难理解他们是怎么实现的。

Interceptor是OkHttp最核心的设计(也是我认为最优雅的设计),它把请求、缓存、桥接等各个功能都解耦成一个个的Interceptor,然后用一条责任链完美地串联在一起。

接下来我们关注发送请求的两个关键步骤:建立连接和发送数据

建立连接

ConnectInterceptor的代码:

@Override public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  Request request = realChain.request();
  StreamAllocation streamAllocation = realChain.streamAllocation();
  // We need the network to satisfy this request. Possibly for validating a conditional GET.
  boolean doExtensiveHealthChecks = !request.method().equals("GET");
  HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
  RealConnection connection = streamAllocation.connection();
  return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

ConnectInterceptor的职责很简单,就是构造一个HttpCodec并放入Interceptor链中,HttpCodec是一个负责对请求和响应进行编码和解码操作的抽象类,所有IO操作都由它来封装实现。ConnectInterceptor会使用RetryAndFollowUpInterceptor生成的StreamAllocation,找到可用的 RealConnection,根据HTTP版本构造对应的HttpCodec实体对象,提供给后面的Interceptor使用。

发送数据

CallServerInterceptor的代码,省略了部分:

@Override public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  HttpCodec httpCodec = realChain.httpStream();
  StreamAllocation streamAllocation = realChain.streamAllocation();
  RealConnection connection = (RealConnection) realChain.connection();
  Request request = realChain.request();

  // 发送Request的Header
  httpCodec.writeRequestHeaders(request);
  Response.Builder responseBuilder = null;
  // 判断是否需要发送Body
  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) 
    if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
      httpCodec.flushRequest();
      // 100-coutinue的处理
      responseBuilder = httpCodec.readResponseHeaders(true);
    }
    if (responseBuilder == null) {
      // 发送Request的Body
      long contentLength = request.body().contentLength();
      CountingSink requestBodyOut =
          new CountingSink(httpCodec.createRequestBody(request, contentLength));
      BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
      request.body().writeTo(bufferedRequestBody);
      bufferedRequestBody.close();
    } else if (!connection.isMultiplexed()) {
      streamAllocation.noNewStreams();
    }
  }
  httpCodec.finishRequest();

  if (responseBuilder == null) {
    // 读取Response的Header
    responseBuilder = httpCodec.readResponseHeaders(false);
  }
  Response response = responseBuilder
      .request(request)
      .handshake(streamAllocation.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();
  int code = response.code();
  // 读取Response的Body
  if (forWebSocket && code == 101) {
    response = response.newBuilder()
        .body(Util.EMPTY_RESPONSE)
        .build();
  } else {
    response = response.newBuilder()
        .body(httpCodec.openResponseBody(response))
        .build();
  }
  if ("close".equalsIgnoreCase(response.request().header("Connection"))
      || "close".equalsIgnoreCase(response.header("Connection"))) {
    streamAllocation.noNewStreams();
  }
  return response;
}

CallServerInterceptor使用前面Interceptor生成的产物,一步步发送Request的Header和Body,再接收Response的Header和Body,最后返回构建好的Response。CallServerInterceptor作为Interceptor链的最后一环,最终实现了Http通讯功能。

总结

最后再总结一下OkHttp的流程,首先用OkHttpClient.Builder构建OkHttpClient,通过newCall方法把配置好的Request转换为可执行的RealCall,根据同步或异步使用不同的调度方式,构建Chain按顺序执行每个Interceptor的操作,最终返回Response。

OkHttp的架构简单优雅,拓展性也因为Interceptor的设计而异常强大,和Retrofit结合使用的话还能发挥更强大的功力。这次对OkHttp的源码简析还有很多地方没有仔细说明,比如RetryAndFollowUpInterceptor失败和重定向的操作、CacheInterceptor的缓存方案等等,感兴趣的同学可以下载OkHttp的源码学习。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容