OkHttp

1、使用方式

在不使用网络请求框架的情况下,我们通常需要经过以下步骤完成一次网络请求:


image.png

[图片上传中...(image.png-279b4-1619598133702-0)]

1、首先build request参数
2、因为不能在主线程请求HTTP,所以你得有个Executer或者线程enqueue后,通过线程去run你的请求
4、得到服务器数据后,callback回调给你的上层

在这个过程中需要做好线程切换,解析读取数据,然后切回主线程,回调给上层,这些过程会有非常多的坑 因此才会有很多的网络开源框架被使用到,下面总结OkHttp 网络框架 的使用方法及原理。

//构建OkHttpClient
                OkHttpClient okHttpClient = new OkHttpClient.Builder()
                        .addInterceptor(new LoggerInterceptor())
                        .connectTimeout(60 * 1000, TimeUnit.MILLISECONDS)
                        .build();

                //构建Request
                Request getRequest = new Request.Builder()
                        .addHeader("name","android")
                        .url("www.baidu.com")
                        .get()  //get 请求
                        .build();


                //创建post 请求
                MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
                String requestBody = "I am white7"; //提交内容为String

                //也可在外部直接创建RequestBody
                RequestBody requestBodyOut = new RequestBody() {
                    @Nullable
                    @Override
                    public MediaType contentType() {
                        return MediaType.parse("text/x-markdown; charset=utf-8");
                    }

                    @Override
                    public void writeTo(BufferedSink sink) throws IOException {
                        sink.writeUtf8("I am Jdqm.");
                    }
                };

                Request postRequest = new Request.Builder()
                        .addHeader("name","white7")
                        .url("www.baidu.com")
                        .post(RequestBody.create(mediaType,requestBody))
                        .build();

                //构建Call
                Call call = okHttpClient.newCall(postRequest);

                //异步请求
                call.enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {

                    }

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

                    }
                });

                //同步请求
                try {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            Response execute = call.execute();
                        }
                    }).start();
                } catch (IOException e) {
                    e.printStackTrace();
                }

可以看到发起一次请求需要OkhttpClient、 Request、 Call 三个对象参与。先看OkHttpClient

2、OkHttpClient

 OkHttpClient(Builder builder) {

    this.dispatcher = builder.dispatcher; //调度器
    this.proxy = builder.proxy; //代理配置
    this.protocols = builder.protocols; //支持协议配置
    this.interceptors = Util.immutableList(builder.interceptors); //请求拦截器
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors); //网络请求拦截器
    this.eventListenerFactory = builder.eventListenerFactory;  //流程监听器
    this.proxySelector = builder.proxySelector; /代理选择器
    this.cookieJar = builder.cookieJar;//Cookie策略,是否保存Cookie
    this.cache = builder.cache;  //缓存配置
    this.socketFactory = builder.socketFactory;//socket配置

    boolean isTLS = false;
    
    if (builder.sslSocketFactory != null || !isTLS) {
      this.sslSocketFactory = builder.sslSocketFactory; //https socket配置
      this.certificateChainCleaner = builder.certificateChainCleaner;
    } else {
      X509TrustManager trustManager = Util.platformTrustManager();
      this.sslSocketFactory = newSslSocketFactory(trustManager);  //https socket配置
      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
    }

    if (sslSocketFactory != null) {
      Platform.get().configureSslSocketFactory(sslSocketFactory);
    }

    this.hostnameVerifier = builder.hostnameVerifier;
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
        certificateChainCleaner);
    this.proxyAuthenticator = builder.proxyAuthenticator;
    this.authenticator = builder.authenticator;
    this.connectionPool = builder.connectionPool; //连接池
    this.dns = builder.dns; //Dns配置
    this.followSslRedirects = builder.followSslRedirects; /是否可以从HTTP重定向到HTTPS
    this.followRedirects = builder.followRedirects; //是否重定向
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    this.callTimeout = builder.callTimeout; //请求超时配置 0代表不会超时
    this.connectTimeout = builder.connectTimeout; //写入超时
    this.readTimeout = builder.readTimeout; //写入超时
    this.writeTimeout = builder.writeTimeout; //写入超时
    this.pingInterval = builder.pingInterval;  /针对HTTP2和web socket的ping间隔
    、、、
   }
  }

可以看到 OkHttpClient 本质是一个集合了多个 请求配置的 配置类,并且提供了 Builder 模式的设计,使用者可以灵活配置。

3、Request

 Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tags = Util.immutableMap(builder.tags);
  }

Request类用来描述请求的参数信息等,包含域名、请求方式、请求头、请求体等一系列信息。和Okhttpclient 一样Request 类也是一个配置类。

4、Call
Call 是一个接口,具体的实现是RealCall

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
    this.timeout = new AsyncTimeout() {
      @Override protected void timedOut() {
        cancel();
      }
    };
    this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
  }

可以看到Call 中关联了 client 和Request。 Call 作为请求的直接发起人,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)); // 调用dispatcher的enqueue方法 创建AsyncCall 对象作为参数并注入responseCallback。
  }

5、Dispatcher 的enqueue 方法分析
从Call 的 enqueue 方法可以得出,最终的请求在Dispatcher 内部:

  //准备队列
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  //运行队列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
...

 void enqueue(call) {
    synchronized (this) {
      readyAsyncCalls.add(call);
    }
    promoteAndExecute();
  }

Dispatcher 类中有两个队列,分别保存 就绪的AsyncCall 和运行的AsyncCall , 在添加完 AsyncCall 后会调用promoteAndExecute 方法。

   private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { //从就绪队列中 取出符合规则的 AsyncCall 调用
        AsyncCall asyncCall = i.next();

        if (runningAsyncCalls.size() >= maxRequests) break; // 正在运行的此call 是否大于 maxRequest
        if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // 相同端口的运行 call 数量是否大于maxRequestsPerHost

        i.remove(); //从就绪队列中移除符合规则的 AsyncCall
        executableCalls.add(asyncCall); 
        runningAsyncCalls.add(asyncCall);  //添加到运行队列中
      }
      isRunning = runningCallsCount() > 0;
    }

    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i); 
      asyncCall.executeOn(executorService()); //调用AsyncCall 的executeOn 方法 并存入一个线程池
    }

    return isRunning;
  }

6、RealCall 的 executeOn 方法

 ...

  void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
        executorService.execute(this); //使用线程执行 AsyncCall
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        eventListener.callFailed(RealCall.this, ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); //从运行队列中移除 AsyncCall
        }
      }
    }


     @Override protected void execute() {
      boolean signalledCallback = false;
      timeout.enter();
      try {
        Response response = getResponseWithInterceptorChain();  //调用getResponseWithInterceptorChain 方法获取Response 
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true; //失败回调
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;  //成功回调
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        e = timeoutExit(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);
      }
    }
...

    }
Response getResponseWithInterceptorChain() throws IOException {
    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);
  }

RealCall 的enqueue 最后通过getResponseWithInterceptorChain 方法获取 Response, 从该方法内部可以看到,其通过注入 连接器 集合 & Request 对象 & RealCall & eventListener & 读写超时 以及index == 0 的参数 实例化了一个 RealInterceptorChain, 通过该对象的proceed 方法获取的响应结果。话不多说,进去看看-->

 RealInterceptorChain 类
   ...
  @Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }


  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

...
 
    // 创建新的 RealInterceptorChain  该方法首次创建的streamAllocation、streamAllocation、 httpCodec 都为空
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);

     //获取 index 位置的 拦截器  如果用户有添加自定义的 拦截器 那么先取得是 自定义的拦截器
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next); //

    
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

在没有添加自定义拦截器,按照以下顺序进行拦截的器调用。

image.png

6.1 RetryAndFollowUpInterceptor

这个拦截器用来进行错误重试和重定向

@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation;

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        response = realChain.proceed(request, streamAllocation, null, null); //重新调用chain 的proceed 
        releaseConnection = false;
      } catch (RouteException e) {
     
        if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
          throw e.getFirstConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {
      
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
       
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

     
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Request followUp;
      try {
        followUp = followUpRequest(response, streamAllocation.route());
      } catch (IOException e) {
        streamAllocation.release();
        throw e;
      }

      if (followUp == null) {
        streamAllocation.release();
        return response;
      }

      closeQuietly(response.body());

      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (followUp.body() instanceof UnrepeatableRequestBody) {
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }

      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(followUp.url()), call, eventListener, callStackTrace);
        this.streamAllocation = streamAllocation;
      } else if (streamAllocation.codec() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;
      priorResponse = response;
    }
  }

在RetryAndFollowUpInterceptor 拦截器中只是重新调用了 realChain.proceed ,不同的是这里新增了一个 StreamAllocation 对象进去,然后交给BridgeInterceptor 处理。

6.2 BridgeInterceptor

这个拦截器是应用和网络交互的一个桥梁。他会根据请求内容在Request中添加一些请求头,这些都是用户未感知到的。同时这个拦截器还会读取Cookie配置,如果有Cookie信息,也会通过请求头带到服务端。

@Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    if (body != null) {
    //对请求体数据添加到 head中
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }

    Response networkResponse = chain.proceed(requestBuilder.build());  //在对请求头数据设置完成后交给下一个拦截器

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }

在完成请求头的配置后,交给CacheInterceptor 拦截器,这里主要处理缓存流程,后面单独将缓存。在缓存拦截器后面是ConnectInterceptor 拦截器:

6.3 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);
  }

可以看到这里创建了httpCodec 和RealConnection 对象,又调用了realChain.proceed ,可见realChain 的功能随着拦截链的调用越来越强了。

6.4 CallServerInterceptor

这是所有拦截器中的最后一个拦截器。在这个拦截器里会进行IO操作与服务器交互。


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

    realChain.eventListener().requestHeadersStart(realChain.call());
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), 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();
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        realChain.eventListener().requestBodyStart(realChain.call());
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
        realChain.eventListener()
            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
      } 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) {
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

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

    int code = response.code();
    if (code == 100) {
      // server sent a 100-continue even though we did not request one.
      // try again to read the actual response
      responseBuilder = httpCodec.readResponseHeaders(false);

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

      code = response.code();
    }

    realChain.eventListener()
            .responseHeadersEnd(realChain.call(), response);

    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;
  }

7、OkHttp 缓存

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容