知识的遗忘速度真是快,以前看源码的时候只是初略看一下,也没有完整的去输出笔记,只是在个别源码处添加注释。这次算是把他重新阅读一下并整理输出。从我们最经常使用的OkHttp框架开始。
使用
导入
1 | implementation 'com.squareup.okhttp3:okhttp:3.13.1' |
目前最新的版本为3.13.1,根据官方的说明,3.13.0后,支持的版本为Android 5.0+(API level 21+) and Java 8+。如果还需要兼容Android 5.0以下的,那就只能使用最后的一个版本,3.12.0。接下来的分析,就是基于okhttp:3.12.0
这个版本进行分析的。
1 | implementation 'com.squareup.okhttp3:okhttp:3.12.0' |
OkHttp 3.13 Requires Android 5+
介绍为啥3.13支持版本为 Android 5+,主要是TLSv1.2为Android 5.0才有的。
网络请求
分析源码之前,先列出网络请求实例代码
1 | package cn.guidongyuan.okhttpstudy; |
同步方法流程
发送请求后,会进入阻塞状态直到收到响应,在主线程中需要创建子线程执行
- 创建OkHttpClient和Request对象
- 将Request封装为Call对象
- 调用Call的execute()发送同步请求,返回Response对象
异步方法流程
onFailure()和onResponse()都是在子线程,如果要更新界面必须要切换到主线程
- 创建OkHttpClient和Request对象
- 将Request封装为Call对象
- 调用Call的enqueue()进行异步请求,返回Response对象
源码分析
上面介绍了OkHttpClient的使用,接下来,就开始深入学习一下其源码,首先从OkHttpClient开始
创建OkHttpClient
1 | OkHttpClient mOkHttpClient = new OkHttpClient.Builder().build(); |
OkHttpClient对象的创建,使用了构建者模式,Builder类为OkHttpClient的内部类,查看build()方法。
1 | public OkHttpClient build() { |
1 | public Builder dispatcher(Dispatcher dispatcher) { |
1 | OkHttpClient(Builder builder) { |
可以看到在Builder中设置的参数,在最后创建OkHttpClient的时候都设置进去。有了OkHttpClient,第二步就是创建请求Request对象了。
创建Request和Call
1 | Request request = new Request.Builder().url(url).build(); |
Request对象的创建,同样使用了构建者模式,设置了请求的url。然后通过newCall去创建Call对象
1 | Call call = mOkHttpClient.newCall(request); |
可以看到,Call为接口,真正的实现类为RealCall。然后就用Call对象去发起请求了,请求分为同步和异步。
同步请求
上面介绍到,同步与异步的使用区别,在于调用的方法,同步为execute();
1 | # RealCall.java |
分析execute()方法,其实现步骤主要以上的三步:
- 同步请求比较简单,只是调用分发器把其放到同步任务执行队列runningSyncCalls中
1 | /** Used by {@code Call#execute} to signal it is in-flight. */ |
- 使用职责链模式进行请求,返回Response,后续会详细介绍
- 同步请求finished()方法比较简单,主要就是把上面添加到runningSyncCalls队列中的RealCall移除掉
异步请求
1 | # RealCall.java |
AsyncCall:可以看到,AsyncCall实现了Runnable
1 | # AsyncCall.java final class AsyncCall extends NamedRunnable implements Runnable { |
1 | // 准备异步执行队列 |
使用线程池执行任务
1 | void executeOn(ExecutorService executorService) { |
启动线程后,在线程的run()方法中,会执行
1 | protected void execute() { |
分析enqueue()方法,其实现步骤主要以上的三步:
- 创建一个AsyncCall的子线程,调用分发器添加到准备队列中
- 从准备队列中取出任务,添加到正在执行的列表中,另外获取一个线程池去执行正在执行的任务
- 使用职责链模式进行请求,返回Response,后续会详细介绍
- 在任务的子线程中执行finished(),又重复执行步骤2
拦截器
OkHttp设计的一个精妙的地方,就是拦截器了,使得职责分明,分工明确,不同的拦截器实现不同的功能。
职责链模式
要想理解好拦截器,就要先理解好一个设计模式:职责链模式。可以参考《大话设计模式》
或者 《Android源码设计模式解析与实战》
中关于职责链模式的介绍,还是比较容易理解的,这里就不详细介绍了。
getResponseWithInterceptorChain()
从该方法的被调用就可以看到,拦截器不区分同步和异步,都是调用该方法,在该方法中,会把自定义以及系统提供的拦截器都添加到列表中,然后创建拦截器链去执行,最后返回响应数据Response。
1 | # RealCall.java |
RealInterceptorChain可以看作是整个拦截器链的控制器,其proceed方法会逐步调用interceptors列表中的拦截器,然后又创建一个下一个拦截器链设置进去,典型的职责链模式的使用。
1 | # RealInterceptorChain.java |
Interceptor有很多不同的实现类,分别有不同的职责,但是intercept()的执行流程基本如下
1 | public Response intercept(Chain chain) throws IOException { |
步骤:
- 创建一系列拦截器,并将其放入一个拦截器list中
- 创建一个拦截器链RealInterceptorChain,并执行拦截器的proceed方法
- 在proceed方法中,获取其中的一个拦截器并创建下一个拦截器链
- 调用intercept执行拦截器的具体实现,在中间会去执行下一个拦截器的操作
RetryAndFollowUpInterceptor
重定向失败重连拦截器
- 创建StreamAllocation对象
- 调用RealInterceptorChain.proceed(…)进行网络请求
- 根据异常结果或者响应结果判断是否要进行重新请求
- 调用下一个拦截器,对response进行处理,返回给上一个拦截器
BridgeInterceptor
桥接拦截器,主要是对发送和返回的数据进行转化
- 是负责将用户构建的一个 Request请求转化为能够进行网络访问的请求
- 将这个符合网络请求的 Request进行网络请求
- 将网络请求回来的响应 Response转化为用户可用的Response.
CacheInterceptor
缓存拦截器,这部分会在接下来介绍缓存机制中重点介绍
ConnectInterceptor
网络连接拦截器,这部分会在接下来介绍连接池中重点介绍
CallServerInterceptor
发送网络请求拦截器,会在下面详细介绍
缓存机制
OkHttp使用缓存非常简单,只需要在创建的使用设置一个缓存对象Cache,缓存逻辑的处理交给了CacheInterceptor拦截器进行处理。
Cache
首先看一下怎样设置使用缓存功能。
1 | mOkHttpClient = new OkHttpClient.Builder() |
查看Cache的构造函数,传入缓存的路径以及缓存的最大大小。另外可以看到其内部是使用DiskLruCache去实现的。也可以看到,创建Cache的时候,会创建其内部类InternalCache,在内部类中去调用Cache的非public方法。
1 | # Cache.java |
先看一下put方法,怎样设置缓存对象
1 | CacheRequest put(Response response) { |
再看一下怎样获取缓存
1 | # Cache.java |
1 | # Entry.java |
CacheInterceptor
上面介绍了怎样获取和设置缓存对象,在OkHttp中,缓存的具体操作就是通过CacheInterceptor缓存拦截器进行执行的。
1 | # CacheInterceptor.java |
从上面可以知道,拦截器的缓存策略,是通过CacheStrategy,CacheStrategy的创建是通过工厂模式创建的,通过不同的判断条件设置其两个参数。如果networkRequest为null,则表示不进行网络请求;而如果cacheResponse为null,则表示没有有效的缓存。
1 | public CacheStrategy get() { |
看了上面的分析,基本可以知道Okhttp的缓存流程,总结如下:
- OkHttp的缓存,使用的是Cache类来实现,其底层使用DiskLruCache,用url的md5作为key,response作为value
- 缓存的获取与写入是通过CacheInterceptor进行操作,其内部创建CacheStrategy作为缓存策略,包含两个属性,networkRequest决定是否连接网络,cacheResponse是否是有效的缓存。
- 如果没有网络且没有缓存,直接返回504
- 没有网络,返回缓存
- 通过下一个拦截器进行网络请求,收到304则直接使用缓存返回
- 把网络请求后的数据保存到缓存中,返回请求的数据
- 另外,OkHttp缓存的写入,使用的是NIO,NIO为非阻塞性,面向的是数据流,可以调高写入与读取的效率。在这里就不详细展开分析了。
连接池
学习OkHttp的另外一个重要知识点,就是连接池了。在连接池中,会维护与服务器进行数据传输的连接数目,并且会自动清除空闲的连接。
ConnectInterceptor拦截器
负责进行连接主机,在这里会完成socket连接,并将连接返回,同样是从拦截器的intercept方法进行分析
1 | # ConnectInterceptor.java |
查看上面创建HttpCodec的方法
1 | # StreamAllocation.java |
具体是怎样找到RealConnection的,可以详细看一下
1 | private RealConnection findHealthyConnection(int connectTimeout, int readTimeout, |
从上面的分析,获取RealConnection的流程,总结如下:
- 在ConnectInterceptor中获取StreamAllocation的引用,通过StreamAllocation去寻找RealConnection
- 如果RealConnection不为空,那么直接返回。否则去连接池中寻找并返回,如果找不到直接创建并设置到连接池中,然后再进一步判断是否重复释放到Socket。
- 在实际网络连接connect中,选择不同的链接方式(有隧道链接(Tunnel)和管道链接(Socket))
- 把RealConnection和HttpCodec传递给下一个拦截器
ConnectionPool连接池类
上面介绍到从连接池中调用get和put,接下来就详细介绍。连接池ConnectionPool的创建,在Builder中默认就创建了
1 | # OkHttpClient.java |
进去ConnectionPool看其get和put方法
1 | public final class ConnectionPool { |
具体回收的判断,可以看cleanupRunnable
1 | private final Runnable cleanupRunnable = new Runnable() { |
1 | long cleanup(long now) { |
通过分析就可以知道,清除算法,使用类似GC中的引用计算算法,如果弱引用StreamAllocation列表为0,则表示空闲需要进行回收。
CallServerInterceptor
向服务发起真正的网络请求,并且接受服务器返回的响应。
1 | public Response intercept(Chain chain) throws IOException { |
总结:
Okhttp框架的分析基本完成,可以看到重点主要是三个,拦截器链、缓存机制以及连接池的使用。
- 拦截器链使用的是职责链模式,将网络请求的不同职责分工到不同的拦截器中,分工明确,并且还可以添加自定义拦截器,扩展方便。
- 缓存机制通过创建Cache,内部通过DiskLruCache去实现。另外在缓存拦截器中通过不同的缓存策划判断返回不同的请求结果。
- 连接池的引入,可以对连接进行管理,避免频繁地建立和断开连接。在连接池中维护了一个线程池清理空闲的连接,判断连接是否可用通过判断引入计算数量。