您的位置:首页 > 理论基础 > 计算机网络

重识OkHttp:从深入了解到源码分析

2017-04-28 09:02 232 查看
前言

本文的分析基于 OkHttp3.4 ,不展示完整的代码示例,具体可以看这个官方例子或者项目中的例子。
https://github.com/square/okhttp/wiki/Recipes
OkHttp作为square公司出品的一个网络请求框架,应该算是目前Android端最火爆的网络框架了。我公司目前的项目中采用的都是Rxjava结合Retrofit进行网络请求的处理,对于底层真正实现网络请求的OkHttp关注的不是很多。最近探究了一下OkHttp的源码,对OkHttp的使用有了一些新的认识,在此做一下总结。

目录

使用篇

OkHttp的优点

网络处理3要素

请求Request

表单FormBody

分块MultipartBody

客户端OkHttpClient

同步请求和异步请求

其他

配置响应缓存

取消请求

Response读取响应结果

总结

分析篇

客户端完整的请求

探究源码

Call的实现类RealCall

RealCall的enqueue( Callback callBack)

Dispatcher的enqueue

getResponseWithInterceptorChain()

同步请求

总结

1

使用篇

1.OkHttp的优点

OkHttp作为当前Android端最火热的网络请求框架,必然有很多的优点。

支持HTTP / 2协议,允许连接到同一个主机地址的所有请求共享Socket。这必然会提高请求效率。

在HTTP / 2协议不可用的情况下,通过连接池减少请求的延迟。

GZip透明压缩减少传输的数据包大小。

响应缓存,避免同一个重复的网络请求。

2.网络处理3要素

对于客户端来讲,我们关注的就是把正确的请求发送到服务端并拿到结果来进行处理。在OkHttp中,我认为可以分为3个部分:

请求类封装客户端发送的请求,包括请求的url,请求方法(主要是GET和POST方法),请求头标题以及请求体requestBody;

响应类封装了服务器响应的数据,包括代码,消息,主体,头等。

OkHttpClient负责发送请求请求并通过同步或者异步的方式返回服务器的响应响应,就好比是一个浏览器。

OkHttp中通过建造者模式来构建OkHttpClient,请求和响应对于客户端来讲,我们不需要过多关注响应是如何构建的,因为这个是OkHttp对响应结果进行了封装处理。我们只关注请求请求和客户端OkHttpClient如何构建即可。

2.1请求 Request

请求采用建模者模式来配置url,请求方法method,header,tag和cacheControl。

设置url。可以是String类型,URL类型和HttpUrl类型。最终都是用到HttpUrl类型。

设置方法,包含get,post方法等。默认的是get方法.post方法要传RequestBody,类似的还有delete,put,patch。

设置头,方法有addHeader(String name,String value),removeHeader(String name),header(String name,String value),headers(Headers headers).headers(Headers headers)调用之后其它的头都会被移除,只添加这一个标题。而头(String name,String value)方法调用之后,其它与这个名称同名的标题都会被移除,只保留这一个标题。

设置标签,设置标签可以用来取消这一请求。如果未指定标签或者标签为null,那么这个请求本身就会当做是一个标签用来被取消请求。

设置cacheControl,这个是设置到请求头中。用来替换其它名称是“Cache-Control”的头。如果cacheControl是空的话就会移除请求头中名是“Cache-Control”的头。



OkHttp采用POST方法向服务器发送一个请求体,在OkHttp中这个请求体是RequestBody。这个请求体可以是:

字符串类型

流流类型

文件类型

表单形式的键值类型

类似Html文件上传表单的复杂请求体类型(多块请求)

RequestBody有几个静态方法用于创建不同类型的请求体:



最终都是相当于重写了RequestBody的两个抽象方法来写入流,如果传递流类型的参数,只要重写这两个抽象方法即可。



例如,我们提交一个String:



提交File:



提交流:



对于提交表单和分块请求,OkHttp提供了两个RequestBody的子类, FormBodyMultipartBody

2.1.1 表单FormBody

FormBody也是采用建造者模式, 这个很简单,添加key-value形式的键值对即可。

添加键值对有两个方法:



例如:



2.1.2 分块MultipartBody

MultipartBody也是采用建造者模式,MultipartBody.Builder可以构建兼容Html文件上传表单的复杂请求体。每一部分的多块请求体都是它自身的请求体,并且可以定义它自己的请求头。如果存在的话,这些请求头用来描述这部分的请求体。例如Content-Disposition、Content-Length 和 Content-Type如果可用就会被自动添加到头。

MIME类型有:



有几个主要的方法:



例如提交一个图片文件:



2.2 客户端OkHttpClient

OkHttpClient采用建造者模式,通过Builder可以配置连接超时时间、读写时间,是否缓存、是否重连,还可以设置各种拦截器interceptor等。

建议在一个App中,OkHttpClient保持一个实例。一个OkHttpClient支持一定数量的并发,请求同一个主机最大并发是5,所有的并发最大是64。这个与OkHttp中的调度器Dispatcher有关,可以设置并发数。本文不对Dispatcher进行讨论。



一个例子:



OkHttpClient支持单独配置,例如原来设置不同的请求时间,可以通过OkHttpClient的newBuilder()方法来重新构造一个OkHttpClient。例如:



3.同步请求和异步请求

上面已经讲了如何创建Request和OkHttpClient,剩下的就是发送请求并得到服务器的响应了。OkHttp发送请求可分为同步和异步。OkHttpClient首先通过Request构建一个Call,通过这个Call去执行同步或者异步请求。



同步方式,调用Call的execute()方法,返回Response,会阻塞当前线程:

异步方式,调用Call的enqueue(CallBack callBack)方法,会在另一个线程中返回结果。



4.其他

4.1 配置响应缓存

为了缓存响应,需要一个可读写并且设置大小Size的缓存目录。缓存目录需要私有,其它不信任的应用不能访问这个文件。

如果同时有多个缓存访问同一个缓存目录会报错。所以最好只在App中初始化一次OkHttpClient,给这个实例配置缓存,在整个App生命周期内都用这一个缓存。否则几个缓存会相互影响,导致缓存出错,引起程序崩溃。

响应缓存采用Http头来配置,你可以添加这样的请求头 Cache-Control: max-stale=3600。 max-age 指的是客户端可以接收生存期不大于指定时间(以秒为单位)的响应。

为了防止响应使用缓存,可以用 CacheControl.FORCE_NETWORK 。为了防止使用网络,采用 
CacheControl.FORCE_CACHE


注意:如果使用FORCE_CACHE禁止使用网络,而响应又没有缓存存在,OkHttp会报504 Unsatisfiable Request 响应错误。

4.2 取消请求

调用Call.cancel()方法可以立即取消一个网络请求。如果当前线程正在写request或者读response会报IO异常。如果不再需要网络请求,采用这种方法是比较方便的。例如在App中返回了上一页。无论是同步还是异步的请求都可以被取消。

4.3 Response读取响应结果

可以通过Response的code来判断请求是否成功,如果服务器返回的有数据,可以通过Response的body得到一个ResponseBody读取。

如果采用ResponseBody的string()方法会一次性把数据读取到内存中,如果数据超过1MB可能会报内存溢出,所以对于超过1MB的数据,建议采用流的方式去读取,如ResponseBody的byteStream()方法。

需要说明的是:

如果ResponseBody的内容不读取的话,不会触发IO流的读取操作

内容读取之后,这个body需要关闭。

5 总结

OkHttp中的很多类都用到了建造者模式,可以根据需要灵活配置。采用建造者模式的有:

OkHttpClient.Builder

Request.Builder

FormBody.Builder

MultipartBody.Builder

Response.Builder

如果单独使用OkHttp进行网络请求,通常需要开发者自己再封装一下,如果不想重复造轮子,Github上面的有一些优秀开源库可以拿来使用(本文只列出star较多的几个):

hongyangAndroid/okhttputils(曾经在项目中用过)

jeasonlzy/okhttp-OkGo

yanzhenjie/NoHttp

2

分析篇

1.客户端完整的请求

OkHttp发送一个请求需要4步:

构建OkHttpClient

构建Request

创建一个Call

执行Call的同步或者异步方法,处理响应。

我们只以一个简单的异步get请求来举例:



Request和OkHttpClient都是我们自己创建的,不再讨论了。就从这个Call来展开讨论。

2.探究源码

2.1 Call的实现类RealCall

Call是一个准备执行的请求,它是一个接口。含有一个内部接口Factory 用于生成Call。



OkHttpClient实现了Call.Factory接口,所以有一个newCall方法,这个方法中干了这么个事:



可以看到,返回了一个RealCall,这个RealCall是OkHttp中Call的唯一实现类。说明我们执行请求,是通过RealCall发出的。

在RealCall的构造方法中,我们还创建了一个拦截器RetryAndFollowUpInterceptor,通过名字我们可以猜测一下这个拦截器的作用是重试和跟进,这个负责是否断线重连和重定向,可以看到这个拦截器跟OkHttpClient有关联,我们可以在配置OkHttpClient的时候配置断线重连等,默认的都是true。

在OkHttp中以Real为前缀的类,都是真正干活的类

2.2 RealCall的enqueue( Callback callBack)方法

OkHttpClient的newCall方法只是创建了一个RealCall,RealCall的enqueue方法传递了一个CallBack用于处理回调,那我们看看这个方法都干了些什么:



从上面可以看出,同一个Call只能执行一次,否则会报错。

client.dispatcher()返回的是与OkHttpClient绑定的一个Dispatcher。这个Dispatcher用来管理请求的调度。在使用篇我们简单的也提到过这个类。这个类主要是用来管理异步请求的调度,同步请求中虽然也参与了,但只是简单的统计正在执行的Call并在Call执行完毕之后做相应的处理。

AsyncCall是RealCall的内部类,继承了NamedRunnable,实际上也是一个Runnable实现类。这个AsyncCall 包装了Callback。它的run()方法中最终会调用它自己的execute()方法。后面我们会讲到AsyncCall的execute()方法。



RealCall的enqueue( Callback callBack)实际上最后调用了Dispatcher的enqueue(AsyncCall call)方法。

2.3 Dispatcher的enqueue方法

Dispatcher的enqueue方法是这样的:



这个方法中首先会判断当前正在执行的Call的数量以及访问同一个主机地址的Call的数量是否在限定范围内。Dispatcher默认的Call的并发数是64,同一个主机地址的并发数是5。这个并发数可以更改。

如果满足条件,就向代表当前正在执行的Call的集合中添加该Call,并且去执行它。否则就会向等待的集合中添加该Call,等待被执行。

executorService()返回的是ExecutorService对象,调用ExecutorService的execute(call)方法实际上最后调用的就是AsyncCall 的execute()方法。



在这个execute()方法中,能获得返回Response,之后做回调处理,Dispatcher也会对Call进行管理。核心的方法是getResponseWithInterceptorChain()。

2.4 核心方法getResponseWithInterceptorChain()





在这个getResponseWithInterceptorChain()方法中,有大量的Interceptors,有开发者自己定义的Interceptor也有OkHttp自己的Interceptor。这些Interceptor都存入到了ArrayList集合,我们在这里就可以大胆猜测一下这个Interceptor应该是顺序执行的。最后创建了一个RealInterceptorChain,通过调用它的proceed(request)方法开始处理原始的request,然后我们就拿到了我们想要的Response。

RealInterceptorChain是Interceptor.Chain的实现类,看这个Real前缀就知道它干的绝对是重要的事。它是一个具体的拦截器链,我们存放在List<Interceptor> interceptors集合中的拦截器之间的传递都要靠它。

它的proceed(request)最终会调用到四个参数的重载方法:



上述代码部分就是这个方法的核心,能够把Request依次传递给下一个Interceptor去处理。

拦截器Interceptor的设计真是很赞,每一个Interceptor在发送Request的时候只处理自己那一部分Request,然后通过RealInterceptorChain的带动传递给下一个Interceptor进行处理,最后一个Interceptor发送完请求得到服务器的响应Response,经过自己的处理之后返回给它之前的那个Interceptor进行处理,依次进行,最后一个处理完毕的Response返回给开发者用户。

形象一点,Interceptor就像是生产线上的工人,Request是物料,Response是产品,RealInterceptorChain是一节一节的传送带。每个工人同时负责处理自己那一部分的Request和Response,由传送带进行传递,各司其职,最后完成一件对用户来讲完美的产品。



我第一次看OkHttp3.4源码的的时候真是一脸懵逼继而叹为观止。后来看了其它文章,才知道原来这个设计模式叫作 责任链模式 。在Android源码设计模式解析与实战这本书中介绍了Android的事件分发处理采用的也是责任链模式。

具体到每个拦截器都是怎么处理Request和Response的,最好自己去看一下,我们就不展开讨论了。

3.同步请求

看了异步请求的调用,同步请求的分析就比较简单最终调用的是getResponseWithInterceptorChain()这个核心方法。



4.总结

本文对OkHttp一个完整的请求过程做了简单的说明,限于篇幅有些地方讲的不是很详细,有兴趣的读者可以自己去探索一下这个流程,尤其是在拦截器那一部分,掌握好了之后对我们平时灵活运用OkHttp会有很大帮助,如配置我们自定义的缓存拦截器,或者在拦截器中监听下载进度,网上也有很多文章可供参考。

参考:

OkHttp官方Wiki文档

从OKHttp框架看代码设计

拆轮子系列:拆 OkHttp
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  源码 博客