源码解析-OkHttp

6 篇文章 0 订阅
3 篇文章 0 订阅

为什么要了解OkHttp?
市面上很多Android开发都在使用OkHttp框架,而且像现在最流行的rxjava+retrofit结合,retrofit中默认使用的网络请求方式也是OkHttp。Okhttp有个强大的机制-拦截器。它可以实现网络监听、请求及相应重写、请求失败重试等功能。

一、OkHttp的简单使用

在查看源码前我们来回顾下OkHttp的使用

    //第一步
    private final OkHttpClient client = new OkHttpClient();
    public void okhttpAsyGet() throws IOException {
        //第二步
        Request request = new Request.Builder()
                .url("http://ip:port/xxxx")
                .build();
        //第三步---同步请求
        Response response = client.newCall(request).execute();
        
        //第三 步---异步请求
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
            }
        });
    }

1.创建一个OkHttpClient对象(创建一个作为全局变量)

2.创建一个request对象,通过内部类Builder调用生成Request对象

3.创建一个Call对象,调用execute(同步请求使用)或者enqueue(异步请求使用)

二、OkHttp源码

前2个步骤都是属于创建的过程。我们就直接从第三步开始看,先看同步请求execute。执行execute的是newCall返回的类,我们来看下newCall方法:

  @Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

newCall方法返回的是RealCall,那么我们就查看下RealCall的

execute同步请求方法:

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

看了下代码很短。但是很精髓,我们一步步来看。

1)首先会进行一个同步判断,看这个executed请求是否已经被执行。

2)然后会执行captureCallStackTrace方法,看名字我们应该知道是用来追踪栈的信息。这里不深究。

3)然后再执行dispatcher的executed方法。executed代码:

  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

可以看到只是将RealCall放到同步请求的队列中。

4)调用getResponseWithInterceptorChain方法。

这一步是很关键的一个方法,获取Http网络请求的返回结果进行返回,并且在这一步中执行了一系列的拦截操作。待会进一步说明。

5)请求执行完后无论成功与否都会调用dispatcher的finished方法,通知分发器任务已经结束了。

在同步请求的执行中,dispatcher没有起到太多的作用,真正值得关注的是getResponseWithInterceptorChain方法。现在我们来看下这个RealCall的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);
    return chain.proceed(originalRequest);
  }

一进来就看到很多Interceptor(拦截器)加进列表中,然后加进Interceptor的chain(锁链)中,这个chain就是最终调用请求的类。我们也可以称它为 拦截器链,我们知道Okhttp的拦截器机制很强大,那么我们来看下这些拦截器都起到什么作用:

1)RetryAndFollowUpInterceptor:失败重试重定向的拦截器,比如一个网络请求失败可以重新指向一个新的url进行请求。

2)BridgeInterceptor:将用户的请求转化为到服务器的请求,将服务器返回的响应转回给用户。进行的是友好输出的响应。可以理解为桥接的作用。

3)CacheInterceptor:缓存的拦截器,负责直接读取缓存或者更新缓存的拦截器。若缓存中有所需的请求响应,就不再执行下面的操作。

4)ConnectInterceptor:负责与服务器连接的拦截器。借助前面RetryAndFollowUpInterceptor分配好的StreamAllocation对象,建立与服务器的连接并且选定所用的交互协议是HTTP1.1还是HTTP2。

5)RetryAndFollowUpInterceptor:负责处理IO。与服务器进行数据的交换。

 

下面我们来看下RetryAndFollowUpInterceptor的源码作为了解。

public final class CallServerInterceptor implements Interceptor {
  private final boolean forWebSocket;

  public CallServerInterceptor(boolean forWebSocket) {
    this.forWebSocket = forWebSocket;
  }

  @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();

    long sentRequestMillis = System.currentTimeMillis();
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return what
      // we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      } else if (!connection.isMultiplexed()) {
        // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from
        // being reused. Otherwise we're still obligated to transmit the request body to leave the
        // connection in a consistent state.
        streamAllocation.noNewStreams();
      }
    }

    httpCodec.finishRequest();

    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      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();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }
}

可以看出代码也不多
1)首先是将http请求头部发送给服务器,如果有body的话会再将boby发送给服务器,而后通过httpStream.finnishRequest()结束http请求的发送。

2)随后便从连接中读取服务的返回响应,构建Response。

3)判断 Connection值是否为close。是则CallServerInterceptor则关闭连接。

4)返回response。

这里值得关注 的是HttpCodec 这个对象,这个对象是主要负责IO处理的,而这个对象的实体类内部就是使用okio。而okio封装了Socket。也就是说其实这个HttpCodec实体类本质上还是对Socket的一层层封装出来的类。当然封装出来的效果就是让我们更容易的使用。

另外说明下,Interceptor的设计是一种分层的思想,和tip/ip协议是有异曲同工之妙。每一层都只需要负责自己这层所该负责的任务,把复杂的任务拆分成一个个具体的独立的任务。这样对以后的扩展很有帮助。

 

下面我们来看下enqueue异步请求和execute同步请求有什么不同:

RealCall的enqueue方法:

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

我们可以看到最后还是调用了diapatcher的enqueue方法,我们进去看下:

Diapatcher的enqueue方法:

  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

看到开始就做了些判断,这个判断我们需要先了解下Diapatcher中的三个变量:

 /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

readyAsyncCalls:在准备的异步请求。

runningAsyncCalls :正在运行的异步请求。

runningSyncCalls :正在运行的同步请求。

我们重新看回enqueue内的判断。

如果正在运行的异步请求数 没有爆满且小于每个Request的请求次数,就直接放进runningAsyncCalls。不是的话就放进readyAsyncCalls。

放进runningAsyncCalls后,会调用线程池的方法去执行:executorService().execute(call);

最后执行RealCall还是会和同步请求一样,到getResponseWithInterceptorChain拦截器的方法中去。

也就是说异步请求和同步请求的不同:只是在这个封装的线程池处理上。

三、总结

从这个图可以看出,不管是同步请求还是异步请求都需要通过拦截器来进行网络数据的获取。

而拦截器内部的操作就包含RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、RetryAndFollowUpInterceptor。

在获取完数据后会在缓存中保存一份。最后通过线程池告诉线程执行UI的操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值