美文网首页
Okhttp源码分析

Okhttp源码分析

作者: Kevin_Lv | 来源:发表于2019-10-18 18:45 被阅读0次

1、说明

HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth.

OkHttp is an HTTP client that’s efficient by default:

HTTP/2 support allows all requests to the same host to share a socket.
Connection pooling reduces request latency (if HTTP/2 isn’t available).
Transparent GZIP shrinks download sizes.
Response caching avoids the network completely for repeat requests.
OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and for services hosted in redundant data centers. OkHttp supports modern TLS features (TLS 1.3, ALPN, certificate pinning). It can be configured to fall back for broad connectivity.

Using OkHttp is easy. Its request/response API is designed with fluent builders and immutability. It supports both synchronous blocking calls and async calls with callbacks
  • 经翻译之后的结果如下
    HTTP是现代应用网络的方式。这是我们交换数据和媒体的方式。高效地执行HTTP可以使您的工作负载更快,并节省带宽。
    OkHttp是一个默认高效的HTTP客户端:
    HTTP/2支持允许对同一主机的所有请求共享一个套接字。
    连接池减少了请求延迟(如果HTTP/2不可用)。
    透明的GZIP压缩了下载文件的大小。
    响应缓存完全避免了网络中的重复请求。
    当网络出现问题时,OkHttp会坚持下来:它会从常见的连接问题中悄然恢复。如果您的服务有多个IP地址,如果第一个连接失败,OkHttp将尝试备用地址。这对于IPv4+IPv6和承载在冗余数据中心中的服务是必要的。OkHttp支持现代TLS特性(TLS 1.3、ALPN、证书固定)。可以将其配置为后退以实现广泛的连接。
    使用OkHttp很容易。它的请求/响应API是用连贯构建器和不变性设计的。它支持同步阻塞调用和带回调的异步调用

2、集成

implementation("com.squareup.okhttp3:okhttp:3.12.0")

3、使用

private OkhttpUtils() {
        client = new OkHttpClient.Builder()
                .readTimeout(5, TimeUnit.SECONDS)
                .build();
    }

    /**
     * 同步请求
     */
    public void synRequest() {
        Request request = new Request.Builder().url("http://www.baidu.com").get().build();
        Call call = client.newCall(request);
        try {
            Response response = call.execute();
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 异步请求
     */
    public void AsynRequest() {
        Request request = new Request.Builder().url("http://www.baidu.com").get().build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {

            }

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

由以上代码看出,Okhttp既支持同步请求也支持异步请求。

  • OkhttpClient
client = new OkHttpClient.Builder()
                .readTimeout(5, TimeUnit.SECONDS)
                .build();

先进入OkHttpClient.Builder。


image.png

Builder是OkhttpClient里的内部类,这里重点关注dispatcher这个类,他根据传入的条件决定了同步和异步的分发。后续详细介绍。完成了OkhttpClient创建后,接下来分析Request的创建。

  • Request
Request request = new Request.Builder().url("http://www.baidu.com").get().build();
image.png

Request也是通过Builder模式创建,封装了一些参数,比如url、请求参数、请求方式等等。

  • Call
 Call call = client.newCall(request);
image.png
image.png

Call是一个接口,Call的调用实际是他的实现类RealCall去调用。


image.png

关注RetryAndFollowUpInterceptorr这个拦截器,后续会分析。

4、同步请求

Request request = new Request.Builder().url("http://www.baidu.com").get().build();
        Call call = client.newCall(request);
        try {
            Response response = call.execute();
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }

同步请求则是调用call.execute(),这里阻塞等待结果


image.png

分析这一句代码:client.dispatcher().executed(this);//这个dispatcher就是在OkhttpClient.Builder()中创建的。先分析Dispatcher


image.png
注意!!!
这里设置了最大的请求数64;主机的最大请求数5;一个数量无上限(假的,因为默认不能超过64)的线程池;以及三个队列。

readyAsyncCalls :就绪中的异步请求队列
runningAsyncCalls:执行中的异步请求队列
runningSyncCalls: 执行中的同步请求队列
接下来分析call.execute()

/** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

这里只是把同步请求加入了runningSyncCalls队列中。


image.png

真正的执行放在getResponseWithInterceptorChain()中。暂不分析这个方法,后面再分析、假定已拿到了Response结果。最终调用到了

client.dispatcher().finished(this);
image.png

至此,同步请求已经分析完了,注意promoteAndExecute()这个方法;这个主要与异步请求有关,下面会分析。

5、异步请求

Request request = new Request.Builder().url("http://www.baidu.com").get().build();
        Call call = client.newCall(request);
        call.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());
            }
        });

异步请求则是调用call.enqueue(new Callback()),不阻塞主线程


image.png

client.dispatcher().enqueue(new AsyncCall(responseCallback));所以Dispather是对我们的进行同步请求、异步请求的分发。这里将我们的Callback进行了AsyncCall包装


image.png
注意,先将请求加入了readyAsyncCalls。而不是runningAsyncCalls
image.png

这个方法就是上面同步请求我们未分析的方法,还记得么?再贴个图吧


image.png
为什么执行异步请求之前也执行了这个方法呢?
这里主要是先判断readyAsyncCalls是否有值,然后如果runningAsyncCalls的请求数如果<64同时主机的最大请求书小于5,就移除readyAsyncCalls队列中的请求,并将这个请求加入runningAsyncCalls队列中。同时加入List<AsyncCall>中,通过遍历executableCalls,逐个通过线程池去执行这个请求
image.png
image.png

asyncCall.executeOn(executorService());asyncCall是RealCall的内部类。它继承NamedRunnable ,实际上就把AsyncCall当作Runnable就行。


image.png

executorService.execute(this);实际把AsyncCall给轮一遍,不,执行一遍!


image.png
也就是执行AsyncCall的execute()方法。
image.png

这个方法与同步请求的执行方法很类似,哇哈哈,又看到了Response response = getResponseWithInterceptorChain();
同理,这里先不做对Response做分析。接下来还是看client.dispatcher().finished(this);


image.png
他来了,他又来了。主要还是看promoteAndExecute()法返回值。
image.png
之前已经分析一遍了,之前主要是分析执行过程。现在分析返回值。
image.png
如果当前有请求未完成(不管同步还是异步)则返回true,反之返回false
image.png
最终总会执行
if (!isRunning && idleCallback != null) {
      idleCallback.run();
    }

idleCallback很明显是个Runnable类型


image.png

看注释、当分发器处于空闲状态下,即没有正在运行的请求,设置回调。异步请求已分析完成。

6、QA

  • okhttp如何实现同步异步请求?
    发送的同步/异步请求都会在dispatcher中管理其状态
  • 到底什么事dispatcher
    dispatcher的作用为维护请求的状态,并维护一个线程池,用于执行请求。


    image.png
  • 异步请求为什么需要两个队列
    Dispatcher看做生产者;ExecutorService看做消费者池;Deque<readyAsyncCalls>缓存;Deque<runningAsyncCalls>正在进行的任务;

7、拦截器

拦截器是OkHttp中提供一种强大机制,它可以实现网络监听、请求以及响应重写、请求失败重试等功能。


image.png
image.png

这个就是okhttp提供的拦截器。构成
不管是同步还是异步,获取请求结果的方法都是同一个

Response response = getResponseWithInterceptorChain();
image.png

这里添加了一系列的拦截器,构成了拦截器链,通过每个拦截的层层处理最终返回给我们Response对象、这里注重分析系统提供给我们的五个拦截器(RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、CallServerInterceptor)。
首先创建了Interceptor.Chain,并执行了proceed方法


image.png

创建了RealInterceptorChain,特殊的地方在于index+1,说明在这里的拦截器调用了下一个的拦截器,把所有的拦截器构成了一个完整的链条。

  • 总结1
    1、创建了一系列拦截器,并将其放入一个拦截器list中
    2、创建一个拦截器链RealInterceptorChain,并执行拦截器链的proceed方法

7.1、RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor 顾名思义就是请求重试、重定向


image.png

这里注意
realChain.proceed(request, streamAllocation, null, null)
他会调用下一个拦截器intercept方法。这里就构成了完整的链条,amazing!!!

  • 总结
    1、在发起请求前对request进行处理
    2、调用下一个拦截器,获取response
    3、对response进行处理,返回给上一个拦截器

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

streamAllocation 是用来建立执行HTTP请求所需网络设施的组件,如其名字所显示的那样,分配Stream。这里的streamAllocation最终在ConnectInterceptor和CallServerInterceptor中才会被使用到。


image.png

当请求重试次数大于默认值20的时候便不再请求。

  • 总结
    1、创建StreamAllocation对象
    2、调用RealInterceptorChain.proceed(...)进行网络请求
    3、根据异常结果或者响应结果判断是否要进行重新请求
    4、调用下一个拦截器,对response进行处理,返回给上一个拦截器

7.2、BridgeInterceptor

image.png

为Request添加头部信息、压缩等、设置Keep-Alive为true等,使之成为一个能正常使用的Request。


image.png

果然又调用了下一个拦截器的incepter
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
进过这一步(解压缩处理等等),我们就能拿到最终的Respnonse

  • 总结
    1、负责将用户构建的一个Request的请求转化为能够进行网络访问的请求
    2、将这个符合网络请求的Request进行网络请求
    3、将网络请求回来的响应Response转化为用户可用的Response

7.3、CacheInterceptor

image.png
image.png

缓存策略采用的DisLruCache


image.png
image.png

封装成Entry类进行缓存。key就是对请求地址做MD5加密


image.png
写入请求信息
现在分析取缓存
image.png
通过cache.get(key)得到快照Snapshot。相应得到Entry对象,entry.resonce 得到Response
现在分析CacheInterceptor
image.png
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

strategy获取我们的缓存策略


image.png

7.4、ConnectInterceptor

image.png

HttpCodec主要是做编解码工作;
RealConnection就是做实际的网络传输的

  • 总结
    1、ConnectInterceptor获取Interceptor传过来的StreamAllocation,streamAllocation.newStream()
    2、将刚才创建的用于网络IO的RealConnection对象,以及对于与服务器交互最为关键的HttpCodec等对象传递给后面的拦截器

接下来分析一下newStream方法


image.png

1、findHealthyConnection得到RealConnection对象进行实际的网络连接
2、通过生成的RealConnection对象生成HttpCodec


image.png
image.png
  • 总结
    1、弄一个RealConnection对象
    2、选择不同的链接方式
    3、CallServerInterceptor

  • ConnectionPool


    image.png

1、产生一个StreamAllocation对象
2、StreamAllocation对象的弱引用添加到RealConnection对象的allocations集合
3、从连接池中获取进行复用
4、回收:

image.png
image.png

4.1、GC
4.2、StreamAllocation的数量
4.3、cleanUpRunnable

  • 总结
    1、okhttp使用了GC回收算法
    2、StreamAllocation的数量会渐渐变成0
    3、被线程池检测到并回收,这样就保持多个健康的keep-alive连接

7.5、CallServerInterceptor

与服务器真正的交互


image.png
  • 一次请求的大致过程
    1、Call对象对请求的封装
    2、dispatcher对请求的分发
    3、getResponseWithInterceptors()方法

相关文章

网友评论

      本文标题:Okhttp源码分析

      本文链接:https://www.haomeiwen.com/subject/syvimctx.html