作者博客
http://www.jianshu.com/u/16925b46816d
前言
本文的分析基于OkHttp3.4,不展示完整的代码示例,具体可以看这个官方例子或者项目中的例子。
https://github.com/square/okhttp/wiki/Recipes
OkHttp作为square公司出品的一个网络请求框架,应该算是目前Android端最火爆的网络框架了。我公司目前的项目中采用的都是Rxjava结合Retrofit进行网络请求的处理,对于底层真正实现网络请求的OkHttp关注的不是很多。最近探究了一下OkHttp的源码,对OkHttp的使用有了一些新的认识,在此做一下总结。
目录
1
使用篇
1.OkHttp的优点
OkHttp作为当前Android端最火热的网络请求框架,必然有很多的优点。
2.网络处理3要素
对于客户端来讲,我们关注的就是把正确的请求发送到服务端并拿到结果来进行处理。在OkHttp中,我认为可以分为3个部分:
OkHttp中通过建造者模式来构建OkHttpClient,请求和响应对于客户端来讲,我们不需要过多关注响应是如何构建的,因为这个是OkHttp对响应结果进行了封装处理。我们只关注请求请求和客户端OkHttpClient如何构建即可。
2.1请求Request
请求采用建模者模式来配置url,请求方法method,header,tag和cacheControl。
OkHttp采用POST方法向服务器发送一个请求体,在OkHttp中这个请求体是RequestBody。这个请求体可以是:
RequestBody有几个静态方法用于创建不同类型的请求体:
最终都是相当于重写了RequestBody的两个抽象方法来写入流,如果传递流类型的参数,只要重写这两个抽象方法即可。
例如,我们提交一个String:
提交File:
提交流:
对于提交表单和分块请求,OkHttp提供了两个RequestBody的子类,FormBody和MultipartBody
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()方法。
需要说明的是:
5 总结
OkHttp中的很多类都用到了建造者模式,可以根据需要灵活配置。采用建造者模式的有:
如果单独使用OkHttp进行网络请求,通常需要开发者自己再封装一下,如果不想重复造轮子,Github上面的有一些优秀开源库可以拿来使用(本文只列出star较多的几个):
2
分析篇
1.客户端完整的请求
OkHttp发送一个请求需要4步:
我们只以一个简单的异步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