彻底掌握网络通信(二十)走进OkHttp3的世界(五)拦截器深入分析二

(二)OkHttp网络请求配置参数详解 前面说到OkHttp网络请求的总体框架。这一章节主要通过解读OkHttpClient的内容,了解可以通过框架配置哪些参数,以及这些参数的意义。让我们对OkHttp框架和HTTP协议之间的关系有更深的了解。 阅读详情

彻底掌握网络通信(一)Http协议基础知识
彻底掌握网络通信(二)Apache的HttpClient基础知识
彻底掌握网络通信(三)Android源码中HttpClient的在不同版本的使用
彻底掌握网络通信(四)Android源码中HttpClient的发送框架解析
彻底掌握网络通信(五)DefaultRequestDirector解析
彻底掌握网络通信(六)HttpRequestRetryHandler解析
彻底掌握网络通信(七)ConnectionReuseStrategy,ConnectionKeepAliveStrategy解析
彻底掌握网络通信(八)AsyncHttpClient源码解读
彻底掌握网络通信(九)AsyncHttpClient为什么无法用Fiddler来抓包
彻底掌握网络通信(十)AsyncHttpClient如何发送JSON解析JSON,以及一些其他用法
彻底掌握网络通信(十一)HttpURLConnection进行网络请求的知识准备
彻底掌握网络通信(十二)HttpURLConnection进行网络请求概览
彻底掌握网络通信(十三)HttpURLConnection进行网络请求深度分析
彻底掌握网络通信(十四)HttpURLConnection进行网络请求深度分析二:缓存
彻底掌握网络通信(十五)HttpURLConnection进行网络请求深度分析三:发送与接收详解
彻底掌握网络通信(十六)走进OkHttp3的世界(一)引言
彻底掌握网络通信(十七)走进OkHttp3的世界(二)请求/响应流程分析
彻底掌握网络通信(十八)走进OkHttp3的世界(三)详解Http请求的连接,发送和响应
彻底掌握网络通信(十九)走进OkHttp3的世界(四)拦截器深入分析

  前面我们详细的分析了RetryAndFollowUpInterceptor,BridgeInterceptor,CacheInterceptor三个拦截器,这篇开始我们将主要分析ConnectInterceptor和CallServerInterceptor两个拦截器

详解ConnectInterceptor

  1. ConnectInterceptor主要作用
      建立客户端和服务端的连接,并将请求传递给CallServerInterceptor处理
  2. 核心代码
  @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);
  }

第4行,我们在RetryAndFollowUpInterceptor中,就创建了StreamAllocation实例,该实例直到ConnectInterceptor才被真正处理,这里就是获得之前创建的StreamAllocation实例;
第8行,通过newStream方法构建客户端到服务端的连接,并返回HttpCodec
第11行,将请求交由CallServerInterceptor处理

可见在ConnectInterceptor中,至关重要的元素是StreamAllocation,通过StreamAllocation我们可以建立客户端到服务端的连接


详解StreamAllocation

  1. StreamAllocation主要作用
    他主要维护客户端请求call,客户端到服务端的连接和客户端到服务端之间的流

  2. StreamAllocation主要成员
    2.1:Address address: 主要描述服务端Host和Port
    2.2:ConnectionPool connectionPool:连接池,该连接池主要维护客户端和服务端建立的连接(RealConnection)
    2.3:Route route:我理解的是地址映射,在客户端到服务端之间寻找一条合适的地址
    2.4:RealConnection connection:一个客户端到服务端的真实连接
    2.5:HttpCodec codec:主要对请求进行编码和对响应进行解码

  3. 核心代码分析

  public HttpCodec newStream(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    int connectTimeout = chain.connectTimeoutMillis();
    int readTimeout = chain.readTimeoutMillis();
    int writeTimeout = chain.writeTimeoutMillis();
    int pingIntervalMillis = client.pingIntervalMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }

第10行,通过findHealthyConnection方法返回客户端到服务端的一条真实的链接RealConnection resultConnection
第12行,通过RealConnection的newCodec方法得到HttpCodec resultCodec,这样ConnectInterceptor就可以利用此处的HttpCodec和RealConnection来和服务端进行交互了

那如何找到一条客户端到服务端的连接?,着重看下findHealthyConnection方法

  private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
      boolean doExtensiveHealthChecks) throws IOException {
    while (true) {
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          pingIntervalMillis, connectionRetryEnabled);

      // If this is a brand new connection, we can skip the extensive health checks.
      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          return candidate;
        }
      }

      // Do a (potentially slow) check to confirm that the pooled connection is still good. If it
      // isn't, take it out of the pool and start again.
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        noNewStreams();
        continue;
      }

      return candidate;
    }
  }

这个方法比较强势,开启一个循环直到找到一个健康的连接为止,那什么才是健康的连接,我们来看下代码
  第5行,通过findConnection方法返回RealConnection candidate
  第10行,如果这个连接是第一次建立,即

candidate.successCount == 0;//successCount表示这个连接被成功建立的次数

那么直接返回这个连接
  第17行,如果这个连接是不健康,则跳过本次循环,调用noNewStreams重新寻找一条连接;所以RealConnection的isHealthy方法则表示一个连接是否健康,这里就不看isHealthy方法了,直接总结下如果符合下述情况则表示为不健康的链接
  1)socket关闭
  2)socket处于半读状态,通过isInputShutdown方法获得; 客户端可以通过shutdownInput()方法关闭输入流,但程序还可以通过该Socket的输出流输出数据
  3)socket处于半写状态,通过isOutputShutdown方法获得; 客户端可以通过shutdownOuput()方法关闭输出流,程序还可以通过该Socket的输入流读取数据
  4)其他 … …

返回来看下findConnection方法

  /**
   * Returns a connection to host a new stream. This prefers the existing connection if it exists,
   * then the pool, finally building a new connection.
   */
  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;
    Route selectedRoute = null;
    Connection releasedConnection;
    Socket toClose;
    synchronized (connectionPool) {
      if (released) throw new IllegalStateException("released");
      if (codec != null) throw new IllegalStateException("codec != null");
      if (canceled) throw new IOException("Canceled");

      // Attempt to use an already-allocated connection. We need to be careful here because our
      // already-allocated connection may have been restricted from creating new streams.
      releasedConnection = this.connection;
      toClose = releaseIfNoNewStreams();
      if (this.connection != null) {
        // We had an already-allocated connection and it's good.
        result = this.connection;
        releasedConnection = null;
      }
      if (!reportedAcquired) {
        // If the connection was never reported acquired, don't report it as released!
        releasedConnection = null;
      }

      if (result == null) {
        // Attempt to get a connection from the pool.
        Internal.instance.get(connectionPool, address, this, null);
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
        } else {
          selectedRoute = route;
        }
      }
    }
    closeQuietly(toClose);

    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
      // If we found an already-allocated or pooled connection, we're done.
      return result;
    }

    // If we need a route selection, make one. This is a blocking operation.
    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
      newRouteSelection = true;
      routeSelection = routeSelector.next();
    }

    synchronized (connectionPool) {
      if (canceled) throw new IOException("Canceled");

      if (newRouteSelection) {
        // Now that we have a set of IP addresses, make another attempt at getting a connection from
        // the pool. This could match due to connection coalescing.
        List<Route> routes = routeSelection.getAll();
        for (int i = 0, size = routes.size(); i < size; i++) {
          Route route = routes.get(i);
          Internal.instance.get(connectionPool, address, this, route);
          if (connection != null) {
            foundPooledConnection = true;
            result = connection;
            this.route = route;
            break;
          }
        }
      }

      if (!foundPooledConnection) {
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }

        // Create a connection and assign it to this allocation immediately. This makes it possible
        // for an asynchronous cancel() to interrupt the handshake we're about to do.
        route = selectedRoute;
        refusedStreamCount = 0;
        result = new RealConnection(connectionPool, selectedRoute);
        acquire(result, false);
      }
    }

    // If we found a pooled connection on the 2nd time around, we're done.
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
      return result;
    }

    // Do TCP + TLS handshakes. This is a blocking operation.
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      reportedAcquired = true;

      // Pool the connection.
      Internal.instance.put(connectionPool, result);

      // If another multiplexed connection to the same address was created concurrently, then
      // release this connection and acquire that one.
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);

    eventListener.connectionAcquired(call, result);
    return result;
  }

第19行,对releasedConnection进行赋值,因为本地有可能已经分配了一个可用的连接,那么我们就可以直接使用,而不需要经过后续处理
第50行,如果本地有可用的连接,则返回该连接
第69行~第77行,Internal.instance的内部实现在OkHttpClient中的静态成员中

static {
Internal.instance = new Internal() {}

这段代码的作用就是在现有的连接池中,根据Address,route来获得一个可以重用的RealConnection
第81行,如果连接池没有可重用的链接,则获得一个Route
第90行,新创建一个RealConnection实例
第96行,如果连接池有可重用的链接,则直接返回该连接
第101行,如果如果连接池没有可重用的链接,当创建了一个新的RealConnection实例之后,对其做tcp和tls握手。tcp握手就是我们常说的三次握手,他位于传输层;tls(安全传输层协议)也是位于传输层,用于在两个通信应用程序之间提供保密性和数据完整性
第102~104行行,通过调用conne方法完成底层socket连接
第111行,将创建的RealConnection放到连接池中

我们这里在简单分析下第102行的connect方法

  public void connect(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
      EventListener eventListener) {
    if (protocol != null) throw new IllegalStateException("already connected");

   /* 省略部分代码 */

    while (true) {
      try {
        if (route.requiresTunnel()) {
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
          if (rawSocket == null) {
            // We were unable to connect the tunnel but properly closed down our resources.
            break;
          }
        } else {
          connectSocket(connectTimeout, readTimeout, call, eventListener);
        }
        establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
        eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
        break;
      } 
	  
	/* 省略部分代码 */  
  }

在connect方法中,调用了一个比较重要的方法establishProtocol

 private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
      int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
    if (route.address().sslSocketFactory() == null) {
      if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
        socket = rawSocket;
        protocol = Protocol.H2_PRIOR_KNOWLEDGE;
        startHttp2(pingIntervalMillis);
        return;
      }

      socket = rawSocket;
      protocol = Protocol.HTTP_1_1;
      return;
    }

    eventListener.secureConnectStart(call);
    connectTls(connectionSpecSelector);
    eventListener.secureConnectEnd(call, handshake);

    if (protocol == Protocol.HTTP_2) {
      startHttp2(pingIntervalMillis);
    }
  }

  private void startHttp2(int pingIntervalMillis) throws IOException {
    socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
    http2Connection = new Http2Connection.Builder(true)
        .socket(socket, route.address().url().host(), source, sink)
        .listener(this)
        .pingIntervalMillis(pingIntervalMillis)
        .build();
    http2Connection.start();
  }

第4行,如果Address地址里面有h2_prior_knowledge字段,则调用startHttp2方法,完成Http2Connection对象http2Connection的创建
第20行,如果协议是http2协议,则完成Http2Connection对象http2Connection的创建

由此可见在调用connect阶段,okhttp会根据协议的版本来决定是否创建Http2Connection对象http2Connection

至此,一个客户端到服务端的连接已经建立起来了;那拿到这个连接之后,还做了什么处理?之前我们提到在StreamAllocation类中有一个重要成员HttpCodec codec,其实这个对象的生成就是在拿到客户端到服务端连接之后,通过newCodec方法来获得的

  RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
      writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
  HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
  public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
      StreamAllocation streamAllocation) throws SocketException {
    if (http2Connection != null) {
      return new Http2Codec(client, chain, streamAllocation, http2Connection);
    } else {
      socket.setSoTimeout(chain.readTimeoutMillis());
      source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
      return new Http1Codec(client, streamAllocation, source, sink);
    }
  }

第3行,如果http2Connection不为空,则构建Http2Codec来用于请求的编码和响应的解码
第9行,反之则构建Http1Codec来用于请求的编码和响应的解码

  Http2Codec:使用HTTP2标准请求进行编码和对响应进行解码
  Http1Codec:使用HTTP1标准请求进行编码和对响应进行解码

当完成HttpCodec 创建之后,我们就可以将这个httpCodec,RealConnection,streamAllocation一起交给下一个拦截器进行处理了。


额外补充一下Http1和Http2的一些知识点
HTTP1.1

  1. 通过Keep_Alive来保持客户端到服务端的连接,达到重用连接的目的;不至于每一次请求都经过握手的过程;http 1.0中默认是关闭的,需要在http头加入"Connection: Keep-Alive",才能启用Keep-Alive;http 1.1中默认启用Keep-Alive,如果加入"Connection: close ",才关闭
  2. 请求和响应都是顺序应答的,加入一个响应或请求阻塞了,那么后续的请求和响应将无法达到,这就是常说的对头阻塞
    HTTP2.0
  3. 多路复用技术,消除了 HTTP 1.x 中并行处理和发送请求及响应时对多个连接的依赖。可客户端和服务器可以把HTTP消息分解为互不依赖的帧,然后乱序发送,最后再在另一端把它们重新组合起来。从而避免不必要的延迟,提升效率,在请求量比较大的场景,客户端也可以轻松使用少量连接完成大量请求数据的传输
    简单描述HTTP2.0

详解CallServerInterceptor

  1. 主要功能
    将请求发送给客户端并接收服务端的响应将请求发送给客户端并接收服务端的响应

  2. 核心代码

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

  第3行,拿到ConnectInterceptor.java中创建的HttpCodec httpCodec,这里我们假设请求是基于http1.1发送的,即httpCodec的实现者为Http1Codec为例
  第4行,拿到ConnectInterceptor.java传递过来的StreamAllocation streamAllocation
  第5行,拿到ConnectInterceptor.java传递过来的RealConnection connection
  第6行,拿到拦截链中的请求Request request
  第10行,回调EventListener接口,表明准备开始写入请求头信息
  第11行,调用Http1Codec的writeRequestHeaders方法将头信息写入BufferedSink中
  第12行,回调EventListener接口,表明写入头信息结束
  第15~43行,如果请求头中有Expect: 100-continue特殊字段,而进行的特殊处理;
针对post一个大数据的请求的时候,一般客户端的头信息会携带Expect:100-continue字段,这时服务器会读取请求的header并返回一个100 continue的响应,如果服务器可以提供这项服务的话(即Server愿意接受数据)。客户端再将http请求发送回去。然后服务器会读取请求的body并且在成功后返回200状态码。
  第45行,调用Http1Codec的finishRequest方法将头信息输入到流中
  第47行,一般情况下responseBuilder是为null的,因为大部分post请求携带的消息体不大
  第49行,构建Response.Builder responseBuilder(响应构建者),用于构建响应
  第52行,构建一个空的Response response,作为服务端的响应
  第60行,当服务端返回100的时候,即当客户端携带了Expect: 100-continue字段进行请求,服务端返回了100-continue
  第61行,读取服务端响应内容的头信息,返回responseBuilder
  第65行,当服务端返回100的时候,即当客户端携带了Expect: 100-continue字段进行请求时,构建响应Response response
  第75行,回调EventListener接口,表明读取响应头信息结束
  第84~87行,一般情况下,response的读取都是在这里获得的;通过调用Http1Codec的openResponseBody方法读取服务端的响应;

至此服务端的响应的内容已经完成获取

当获得响应之后,在通过上一个拦截器依次向上传递,经过

  1. CacheInterceptor拦截器的进行缓存处理
  2. BridgeInterceptor拦截器的对响应的最后查漏补缺字段
    最终交给上册完成整个流程

有的同学会问,为什么要看源码,我们会用不就好了吗

  其实不是的,会用只是你掌握了秘籍的招式,但是要想把招式打的更好,就需要多想多学,通过精读httpurlconnect,httpclient和okhttp3三个框架,不仅仅学习到了三个框架的精髓,也补充了很多网络协议方面的知识,同时也发现了当前项目中的网络框架的一些缺陷;

  框架里面还有很多方面没有涉及到,有需要的同学可以联系我,我再补充下

OkHttp 3.3.1网络通信实战指南 OkHttp是由Square公司开发的一款开源HTTP客户端库,最早发布于2013年,旨在为Java和Android平台提供高效、简洁的网络通信解决方案。随着移动互联网的发展,OkHttp不断迭代,3.3.1版本在当时广泛应用于Android项目中,具备稳定性和良好的协议兼容性。// 示例:创建一个基本的OkHttpClient实例该版本在设计上强调简洁性与高性能,支持现代HTTP协议标准,成为许多开发者构建网络请求模块的首选工具。 阅读详情

相关推荐

【Frida】【Android】08_爬虫之网络通信库okhttp3

每个网络请求和接收不管是GET还是PUT/POST等数据传输方式都必须经过okhttp3本身存在的五大拦截器,因此Interceptor是一个绝佳的Hook点,可以同时打印输出请求和响应。这是因为,App只有一个全局对象client,一般在App启动的较早时机被创建,如果采用attach模式Hook okhttpClient,大概率会一无所获,因此只能用。前文已经说过,拦截器方式,可以更方便的实现请求内容的打印,我们如果将拦截器注入到目标APP中,将更通用而方便。,将打印请求和响应相关的内容。

kinghzking的专栏 2511

王学岗OKHttp原理讲解

看下类RealCall // // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package okhttp3; import java.io.IOException; import java.util.ArrayList; imp...

qczg_wxg的博客 826

OkHttp与Okio网络通信库完整实战指南

在现代移动与Java网络编程中,OkHttp与Okio是两个密不可分的核心库。OkHttp 是一个高效、简洁的 HTTP 客户端,广泛应用于 Android 及 Java 后端项目中,具备支持同步/异步请求、连接池、缓存、拦截器等高级特性。其底层依赖于 Okio 进行 I/O 操作,Okio 是由 Square 公司开发的轻量级 I/O 框架,通过封装 Java 原生 IO/NIO,简化了流式数据的读写操作,提升了性能与代码可维护性。

weixin_42453228的博客 807

H2_PRIOR_KNOWLEDGE in OKHttp

H2_PRIOR_KNOWLEDGE refers to a connection mode used to establish an HTTP/2 connection between a client and a server.

多头注意力探索Now 769

OkHttp3+Okio+Gson网络通信三件套实战集成

就这么一个方法,却撑起了整个扩展体系。每一个拦截器都可以:修改请求头(如添加Token)修改请求体(如压缩JSON)缓存响应结果日志记录自动重试Mock数据测试……关键是,这些功能互不影响,还能自由组合顺序。这就是典型的责任链模式(Chain of Responsibility Pattern)。

weixin_30598047的博客 341

OkHttp3网络请求封装实战指南

Square公司出品的OkHttp3,并非第一个HTTP客户端库,但它却是目前唯一能同时满足性能、安全性与扩展性的选择。想象一下:你的App需要和10个不同的后端服务通信,有的走HTTPS,有的还在用HTTP(比如本地调试);有些接口要加统一头,有些需要自动重试;还要记录日志、防止内存泄漏、支持离线缓存……如果没有一个统一的管理方案,代码很快就会变成“意大利面条”。而OkHttp3的设计哲学就是:“把复杂留给自己,把简洁交给开发者”。它的核心特性包括:✅连接池复用。

weixin_28913879的博客 926

读源码之Okhttp3.x

定义个人解读 OkHttp is HTTP client for Java and Kotlin. 定义其一是说OKHttp是为Java和Kotlin(4.x版本已经用Kotlin重写)平台打造的高效网络框架,自不必多说;其二OkHttp是Http 客户端,这里面包含的知识就很多了: 专注于Http协议,不适用于其他协议;专注于客户端Http通信需求,不适用于服务端。 支持所有的Htt...

u012204388的博客 1134

OkHttp3网络通信工具包实战(含Okio与Gson)

简介:OkHttp3是由Square开发的高效HTTP客户端库,广泛用于Android和Java应用中的网络请求处理。本资源“okhttp3工具.rar”集成OkHttp3 v3.11、Okio v1.14及Gson库,涵盖高性能网络通信与数据序列化核心组件。通过连接池、缓存机制、拦截器链和异步请求等特性,OkHttp3显著提升网络性能;Okio提供高效的I/O操作支持,优化数据读写与文件处理;Gson则实现Java对象与JSON之间的便捷转换。三者结合为开发者构建稳定、高效的网络应用提供了完整解决方案。

weixin_42350014的博客 370

okHttp3 Protocol

okHttp3 Protocol public enum Protocol { HTTP_1_0("http/1.0"), HTTP_1_1("http/1.1"), SPDY_3("spdy/3.1"), HTTP_2("h2"), H2_PRIOR_KNOWLEDGE("h2_prior_knowledge"), QUIC("quic"); p

相公 2448

python音频信号分析

声音以具有诸如频率、带宽、分贝等参数的音频信号的形式表示,典型的音频信号可以表示为幅度和时间的函数

MAY的博客 2785

MNE-python 进行源定位教程(一)配置环境

根据MNE官网提供的教程进行复现,遇到各种问题的记录。 配置环境 python3.8 + Anaconda + pycharm MNE-Python 是一个开源 Python 模块,用于处理、分析和可视化功能性神经影像数据(EEG、MEG、sEEG、ECoG 和 fNIRS)。 根据官方建议,将MNE-python安装在anaconda的虚拟环境中。 1. 安装Anaconda 过程点击这里 2. 配置pycharm 2.1 导入MNE-python工具包 在pycharm的菜单栏里打开VCS-Create

zQIANYUN的博客 3173

OKHttp五大拦截器

文章目录【1】五大拦截器总体概述一、责任链设计模式①定义:②为什么要使用责任链模式【2】拦截器的工作流程一、默认的5大拦截器有哪些?【3】RetryAndFollowUpInterceptor拦截器一、重试二、重定向【4】BridgeInterceptor 桥接拦截器【5】CacheInterceptor缓存拦截器【6】ConnectInterceptor连接拦截器【7】CallServerInterceptor请求服务连接器【8】总结 【1】五大拦截器总体概述 OkHttp最核心的工作是在 getResp

zjcoming 7217

CanOpen PDO映射实战:从原理到配置的完整指南

本文详细解析了CanOpen PDO映射的原理与配置流程,从基础通信机制到实战配置步骤,涵盖禁用PDO、清空映射、逐项映射和使能PDO等关键环节。特别针对西门子G120变频器、台达伺服和倍福CX控制器等不同厂商设备,提供了具体的配置技巧和常见问题排查指南,帮助工程师快速掌握多厂商设备间的PDO通信配置。

weixin_30718391的博客 517

彻底掌握网络通信(十九)走进OkHttp3的世界(四)拦截器深入分析

彻底掌握网络通信(一)Http协议基础知识 彻底掌握网络通信(二)Apache的HttpClient基础知识 彻底掌握网络通信(三)Android源码中HttpClient的在不同版本的使用 彻底掌握网络通信(四)Android源码中HttpClient的发送框架解析 彻底掌握网络通信(五)DefaultRequestDirector解析 彻底掌握网络通信(六)HttpRequestRetryHa...

yi_master的博客 682

RT-Thread学习笔记(5):SPI驱动框架分析

1、两个设备模型 在spi驱框架中,一般会自己初始化一个总线设备(rt_spi_bus),当我们通过挂载后,会相应注册一个从设备(rt_spi_device)。 1、总线设备模型 struct rt_spi_bus { struct rt_device parent; rt_uint8_t mode; const struct rt_spi_ops *ops; struct rt_mutex lock; struct rt_spi_device *owner; };

BLBQ962464的博客 1万+

FISCO BCOS平台的用户权限控制与分类管理

在区块链技术日益广泛应用的今天,如何管理与控制不同用户的权限,确保系统的安全性与高效性,成为了区块链平台不可忽视的一部分。FISCO BCOS作为一款高效的联盟链平台,通过细粒度的权限控制机制,提供了对不同用户权限的有效管理。本文将深入探讨FISCO BCOS如何实现用户的权限控制,并对不同用户进行分类与权限管理。FISCO BCOS平台通过精密的权限控制机制,不仅提供了对用户表和系统表的细粒度权限管理,还通过工具与接口为开发者和管理员提供了便捷的权限配置与查询功能。

2201_75798391的博客 1062

彻底掌握网络通信(十八)走进OkHttp3的世界(三)详解Http请求的连接,发送和响应

彻底掌握网络通信(一)Http协议基础知识 彻底掌握网络通信(二)Apache的HttpClient基础知识 彻底掌握网络通信(三)Android源码中HttpClient的在不同版本的使用 彻底掌握网络通信(四)Android源码中HttpClient的发送框架解析 彻底掌握网络通信(五)DefaultRequestDirector解析 彻底掌握网络通信(六)HttpRequestRe...

yi_master的博客 1255

Claude Code 新手安装指南,手把手教你怎么安装使用

如果你是企业内网用户,或者需要自定义 API 端点,那可以用手动配置 API Key 的方式。

篮球界最会写bug的人;程序猿中打球最菜的人。 490

OKhttp拦截器

前言 之前我们结合设计模式简单说了下OkHttp的大体流程,今天就继续说说它的核心部分——拦截器。 因为拦截器组成的链其实是完成了网络通信的整个流程,所以我们今天就从这个角度说说各拦截器的功能。 首先,做一下简单回顾,从getResponseWithInterceptorChain方法开始。 简单回顾(getResponseWithInterceptorChain) internalfungetResponseWithInterceptorChain():Response{ //...

静待岁月的博客 1144

Vivado ILA抓DDR信号(各种IO信号:差分、ISERDES、IOBUFDS等)

Vivado ILA抓DDR信号(各种IO信号:差分、ISERDES、IOBUFDS等)

千歌叹尽执夏的博客 1102

rk3568 rk628桥片 rgb2hdmi

RK628 是瑞芯微全新推出的视频桥接芯片,支持丰富的显示接口,支持高分辨率大屏产品转换需求。基于DRM、FB的显示框架易于使用。 输入信号支持:HDMI、RGB、BT.1120,输出信号支持 HDMI、GVI、Dual LVDS、Dual MIPI、MIPI-CSI、RGB、BT.1120,最高支持4K 60fps 输入/输出转换,并且支持输入到输出 1/4~4倍的缩放。

weixin_35723192的博客 452

okhttp之拦截器

okhttp的拦截器是项目中的精髓代码,今天我们来具体分析一下,base4.9.1版本 我们已经知道拦截器是在RealCall中添加的: val interceptors = mutableListOf<Interceptor>() interceptors += client.interceptors interceptors += RetryAndFollowUpInterceptor(client) interceptors += BridgeInterce

qq_16548551的博客 4278

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Android Okhttp3基本使用

I.简介 HTTP是现代应用常用的一种交换数据和媒体的网络方式,高效地使用HTTP能让资源加载更快,节省带宽。OkHttp是一个高效的HTTP客户端,它有以下默认特性: 支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接 连接池减少请求延时 透明的GZIP压缩减少响应数据的大小 缓存响应内容,避免一些完全重复的请求 当网络出现问题的时候OkHttp依然坚守自己的职责,它会...

假装你是大灰狼的专栏 2万+

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

精品连载丨安卓 App 逆向课程之三 frida 注入 Okhttp 抓包上篇

“ 阅读本文大概需要 3 分钟。 ”前言抓包常常是Android协议分析的第一步,抓不到包困扰着众多爬虫工程师,因此很有必要抽丝剥茧,了解和学习Android的网络通信相关知识,并且打算...

静觅 4753

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

okhttp3源码初探

okhttp3源码初探简介GET请求使用源码阅读发起请求eventListener的由来真正的网络请求 简介 okhttp3 是目前android平台最流行的网络访问框架,而目前大火的retrofit底层封装的也是OKHttp。所以可以这么说,okhttp是我们做android 开发的过程中绕不过的一座大山。 但是由于okhttp实在太易用了,几句代码就可以完成网络请求的发起,导致我们很少去了解o...

蓝冷然的博客 666

Android OkHttp 5个内置拦截器详解

OkHttp 内部实现了5个核心拦截器,它们按照固定顺序组成拦截器链,共同完成HTTP请求的完整生命周期处理。下面我将详细解析这5个内置拦截器的工作原理和功能。

专注于移动开发的干货分享。提供高效解决方案、避坑指南与前沿技术解析。为开发者创造价值。 886
上一篇: 彻底掌握网络通信(十九)走进OkHttp3的世界(四)拦截器深入分析
下一篇: 彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(一)基本使用
yi_master
博客等级 码龄10年 283粉丝 104原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值