阅读 281

OkHttp源码剖析(一) 初识okhttp

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

OkHttp源码剖析(一) 初识okhttp

OkHttp源码剖析(二) 设计模式下的okhttp

OkHttp源码剖析(三) 任务调度器Dispatcher

OkHttp源码剖析(四) 报文读写工具ExchangeCodec

OkHttp源码剖析(五) 代理路由

使用示例

本篇主要基于okhttp-4.9.0进行分析。下面是OkHttp主要的使用示例。

        OkHttpClient client = new OkHttpClient.Builder().build();
        Request request = new Request
                .Builder()
                .url("https://www.yanfriends.com")
                .build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
​
            }
​
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.e("okhttp response", response.body().string() );
            }
        });
复制代码

架构图

OkHttp的流程图大致如下图所示,这里先有个大致感观,每一环的细节都会在接下来的章节中说到,再看完所有章节后回过头来再看下,会对OkHttp的整个流程架构有清晰的认识。

本质理解

http的连接本质上是个socket,根据http协议,通过socket包装发送请求并获得返回结果。

网路连接库一开始的样子如下代码所示,其实只要符合Http协议的请求,就可以和网络进行交互,类似于OkHttp的网络请求库,帮助开发者方便和屏蔽了Http协议中类似于请求头,重连、合并、代理、返回结果解析等等Http协议细节的应用层实现。

        val path = "http://www.baidu.com/"
        val host = "www.baidu.com"
        var socket: Socket? = null
        var streamWriter: OutputStreamWriter? = null
        var bufferedWriter: BufferedWriter? = null
        try {
            socket = Socket(host, 80)
            streamWriter = OutputStreamWriter(socket.getOutputStream())
            bufferedWriter = BufferedWriter(streamWriter)
            bufferedWriter.write("GET $path HTTP/1.1\r\n")
            bufferedWriter.write("Host: www.baidu.com\r\n")
            bufferedWriter.write("\r\n")
            bufferedWriter.flush()
            val myRequest = BufferedReader(InputStreamReader(socket.getInputStream(), "UTF-8"))
            var d = -1
            while (myRequest.read().also({ d = it }) != -1) {
                print(d.toChar())
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
​
复制代码

OkHttpClient

OkHttpClient是整个OkHttp的配置中心。

所有的call请求都会共享OkHttpClient中的配置,如出错重试、连接池、日志、各种拦截器等。

OkHttpClient实现看Call.Factory接口,是Call的工厂类,生产 Call并通过 Call 来发起 HTTP Request 获取 Response。

OkHttpClient应该被共享,使用时保持单例,因为每个Client 都会有一个自己的连接池和线程池,复用 Client 可以减少资源的浪费。

OkHttpClient 中的配置主要有:

  • Dispatcher dispatcher:用于调度后台发起的网络请求的调度器, 有后台总请求数和单主机总请求数的控制。
  • List<Protocol> protocols :支持的应用层协议,即 HTTP/1.1、 HTTP/2 等。
  • List<ConnectionSpec> connectionSpecs :应用层支持的 Socket 设置,即使用明文传输(用于 HTTP)还是某个版本的 TLS(用于 HTTPS)。
  • List<Interceptor> interceptors :大多数时候使用的 Interceptor 都应该配置到这里。
  • List<Interceptor> networkInterceptors:直接和网络请求交互 的 Interceptor 配置到这里,例如如果你想查看返回的 301 报文或者未解压 的 Response Body,需要在这里看。
  • CookieJar cookieJar:管理 Cookie 的控制器。OkHttp 提供了 Cookie 存取的判断支持(即什么时候需要存 Cookie,什么时候需要读取 Cookie,但没有给出具体的存取实现。如果需要存取 Cookie,你得自己写实现,例如用 Map 存在内存里,或者用别的方式存在本地存储或者数据库。
  • Cache cache :Cache 存储的配置。默认是没有,如果需要用,得自己配置出 Cache 存储的文件位置以及存储空间上限。
  • HostnameVerifier hostnameVerifier :用于验证 HTTPS 握手过程 中下载到的证书所属者是否和自己要访问的主机名一致。
  • CertificatePinner certificatePinner :用于设置 HTTPS 握手 过程中针对某个 Host 额外的的 Certificate Public Key Pinner,即把网站证 书链中的每一个证书公钥直接拿来提前配置进 OkHttpClient 里去,作为正 常的证书验证机制之外的一次额外验证。
  • Authenticator authenticator :用于自动重新认证。配置之后,在 请求收到 401 状态码的响应是,会直接调用 authenticator ,手动加 入 Authorization header 之后自动重新发起请求。
  • boolean followRedirects:是否允许重定向
  • boolean followSslRedirects :不是是否自动 follow HTTPS URL,是在重定向时发生了协议切换 ,是否允许,默认 true。
  • 重定向的意思,而是是否自动 follow 在 HTTP 和 HTTPS 之间切换的重定向。
  • boolean retryOnConnectionFailure :在请求失败的时候是否自动 重试。注意,大多数的请求失败并不属于 OkHttp 所定义的「需要重试」, 这种重试只适用于「同一个域名的多个 IP 切换重试」「Socket 失效重试」 等情况。
  • int connectTimeout :建立连接(TCP 或 TLS)的超时时间,默认十秒; int readTimeout :发起请求到读到响应数据的超时时间,默认十秒; int writeTimeout :发起请求并被目标服务器接受的超时时间。(因为有时候对方服务器可能由于某种原因而不读取你的 Request),默认十秒;

粗识okhttp

OkHttp中的细节很多,这里不过分深入,只需有个大致的结构认识即可,一些细节会在接下来的文章中具体分析。

RealCall初始化

使用OkHttpClient.newCall()创建RealCall和Transmitter类,Transmitter类可以将okhttp和网络层连接起来。

 static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
  }
复制代码

RealCall.execute()/RealCall.enqueue()

  • 调用Dispatcher.executed()/enqueue()将call加入到执行的Call队列中,以控制Call的启动、终结和取消。

  • 调用getResponseWithInterceptorChain()开始向服务器发送请求并获取回应

    @Override public void enqueue(Callback responseCallback) {
        ...
        transmitter.callStart();
        client.dispatcher().enqueue(new AsyncCall(responseCallback));
      }
    复制代码

RealCall.getResponseWithInterceptorChain()

OkHttp将各种请求信息的封装写在了不同层的Interceptor中,根据开放方法的参数设置,请求时层层添加http协议规定的信息,最终将请求发出。getResponseWithInterceptorChain()方法的主要逻辑如下:

  1. 添加自定义的interceptor

  2. 依次添加自定义interceptor、RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、自定义的networkInterceptor(如Facebook的stetho),

  3. 创建RealInterceptorChain对象,之后调用RealCall.RealInterceptorChain.proceed()方法,按照责任链序依次调用每个拦截器处理逻辑

Response getResponseWithInterceptorChain() throws IOException {
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    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();

    try {
      Response response = chain.proceed(originalRequest);
      ...
      return response;
    } catch (IOException e) {
      ...
    } 
  }
复制代码

RealInterceptorChain.proceed()

  1. 检查自定义的interceptor是否合法

  2. 创建新的RealInterceptorChain,注意index+1

  3. 获取index当前的interceptor,并调用interceptor.intercept()方法交给Interceptor处理,获取返回值,思考一下为什么不能用循环

  4. 要求所有自定义的interceptor要调用RealInterceptorChain.proceed(),否则会造成请求无法发出

  5. 判断response或者response.body()是否为空

public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      throws IOException {
    ...
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (exchange != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    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;
  }
复制代码

参考

square.github.io/okhttp/

zhuanlan.zhihu.com/p/58093669

www.jianshu.com/p/8d69fd920…

juejin.cn/post/684490…

文章分类
Android
文章标签