2.okhttp3.0
整体流程:
1).创建okhttpclient客户端对象,表示所有的http请求的客户端的类,执行时只会创建一次,作为全局实例保存,只会使用一个单例对象;
2).创建request对象,封装了请求报文信息,包括了url地址,请求方法,各种请求头,内部通过build链式创建我们的对象;
3).通过request的newcall方法得到一个RealCall,代表我们一个实际的http请求,连接我们request和response的桥梁,有了这个realcall,才能实现我们下面的同步或者异步请求操作
4).决定同步还是异步通过Dispatcher,内部维护了一个线程池,用于执行我们的网络请求,包括同步和异步;realcall在执行任务把请求添加到dispatcher中,在dispatcher三个队列来维护我们的同步异步请求
5).不管是同步还是异步,都是通过拦截器练来进行真正服务器数据获取,构建拦截器链通过依次执行拦截器链中的每个拦截器来将我们服务器获取的数据返回
6).RetryAndFollow,负责两部分逻辑:在网络请求失败后的重试;服务器返回当前请求需要重定向的时候会直接发起请求
7).Bridge:设置请求内容长度,内容编码,gzip压缩,也可以添加我们的cookie,为他设置其他的报头等,主要是一些请求前的操作
8).cache:缓存管理,当我们网络请求有符合要求的请求,直接返回,不需要网络请求
9).Connect:为当前请求找到一个合适的链接;有可能会复用已有的链接,涉及到连接池
10).Callserver:向我们服务器发送真正的网络请求,读取后并返回,做得就是网络链接
源码关键点:
初始化OkhttpClient,初始化Request,用OkhttpClient.newCall(reqeust)获取Call,在用call执行同步或者异步方法。
1).OkhttpClient:build模式初始化;Build()方法中初始化了dispatcher,Connectionpool(连接池,客户端和服务器的链接抽象为一个connection,每一个connection都放在Connectionpool中统一管理,当你请求的URL是相同的可以复用;Connectionpool实现了哪些网络链接保持打开状态,哪些是可以复用的这些策略的设置);包含了newCall(reqeust)方法
2).Request:也是build模式初始化;默认指定请求方式GET。
3).RealCall构造方法中包含client,request;retryAndFollowUp拦截器初始化
4).同步请求:call.execute():内部调用
client.dispatcher.execute(this);-->runningSyncCalls.add(call);
getResponseWithInterceptroChain();
在finally中client.dispatcher().finished(this);-->runningSyncCalls.remove(call);
所以:dispatcher在同步请求中只做了加入队列跟移除队列操作
5).异步请求:call.enqueue():内部调用client.dispatcher().enqueue(new AsyncCall())->runningAsyncCalls.add(call)并executorService().execute(call)或者readyAsyncCalls.add(call);
AsyncCall是个Runable,run中执行getResponseWithInterceptroChain();
在finally中client.dispatcher().finished(this)-->runningAyncCalls.remove(call);
promoteCalls()-->runningAsyncCalls.add(call)并executorService().execute(call)
(6).dispatcher:维护请求的状态,并且维护一个线程池用于执行请求,线程池初始化中coolpoolsize为0;队列数为Interger.Max;时间为60s,也就是当所有线程空闲60s后会被全清除
(7).拦截器:应用程序拦截器,系统内部拦截器(5个),网络拦截器。
发送一个http请求时会通过一个拦截链去实现;RetryAndFollowUpInterceptor是重试和失败重定向拦截器,主要做一些初始化工作和初始化streamAllcation对象传递给后面的拦截器;BridgeInterceptor(桥接和适配器拦截器)和CacheInterceptor他们两的职责主要用来补充用户创建请求当中缺少的请求头和处理缓存的功能;ConnectInterceptor和CallServerInterceptor是网络请求中的关键,其中CallServerInterceptor主要是负责将我们的http请求写入网络的io流当中并且从网络的io流中读取服务器返给客户端的数据;而我们的ConnectInterceptor这个连接拦截器主要用于建立可用的连接,是CallServerInterceptor的基础。这就是拦截器的大体流程。
拦截器链介绍:
RealCall中的getResponseWithInterceptroChain()->List中加入拦截器,并传入RealInterceptorChain构造函数中,然后调用chain.proceed(request)执行请求->创建新的RealInterceptorChain next,参数为index+1;执行当前拦截器intercepor(next)。
每一个拦截器又会调用传入的拦截器链的chain.proceed()方法;从而达到遍历拦截器链
拦截器总结:
a.在发起请求前对request进行处理
b.调用下一个拦截器,获取response
c.对response进行处理,返回给上一个拦截器
(8).RetryAndFollowUpInterceptor:
a.创建streamAllocation:用于获取连接服务端的connection和用于服务端传输的输入输出流;主要传到之后的ConnectInterceptor拦截器所用。
b.调用RealInterceptorChain.proceed()进行网络请求
c.根据异常结果或者相应结果判断是否需要重连
d.对response进行处理返回给上一个拦截器
在while(true)循环中获取response,并对response解析,重连,次数不大于20,当大于20次,streamAllocation.release();并抛出异常退出循环
(9).BridgeInterceptor:
a.设置内容长度,编码方式,gzip等处理请求头部信息
其中有个Connection:keep-alive
b.调用拦截器链chain.proceed()获取response
c.网络请求从服务器返回给我们的response转化为用户可以使用的response,例如如果支持gzip压缩,服务器返回的response在这里进行解压过程
视频中ppt给的三个步骤:
a.是负责将用户构建的一个request请求转化为能够进行网络访问的请求
b.将这个符合网络请求的Request进行网络请求
c.讲网络请求回来的响应response转化为用户可用的response
(10).CacheInterceptor:
使用:在初始化okhttpclient去配置cache类
Cache类
初始化:this.cache=DiskLruCache.create();
有个内部类internalCache->回调方法get,put,remove,update,都是调用Cache的get,put,remove,update;
put方法:最核心还是通过DiskLruCache算法
CacheRequest put(Response response){
a.获取response中requestMethod
b.不缓存非“GET”方法
c.创建Entry实例:url,头部(请求头部和响应头部),方法,协议,code,message,请求时间,响应时间,等所有属性
d.创建DiskLruCache.Editor来写入,key值为url的md5值,value为entry;调用entry.writeTo(editor)来写入
e.return new CacheRequestImpl(editor);写入body
}
get方法:从缓存中读取我们的恶响应体response
Response get(Request request){
a.获取url的md5值作为key
b.DiskLruCache.Snapshot snapshot = cache.get(key);
c.如果snapshot不为空,则以snapshot为值新建Entry,通过entry获取Response
d.再请求 响应是否匹配
}
okhttp内部维护着一个清理线程池,来实现对缓存文件的清理和管理功能
CacheInterceptor:
Response intercept(Chain chain){
1).通过cache.get获取缓存response,可能为空
/**
CacheStrategy(包含networkRequest和cacheResponse:networkRequest为空表示不走网络.cacheResponse为空表示不缓存);
factory()方法中有一项如果request中不要求缓存或者是可选择的get则返回new CacheStrategy(request,null);通过缓存策略能知道是否直接返回还是继续往下走拦截器链
**/
2).CacheStrategy strategy = new CacheStrategy.factory(request,cacheResponse);
3).在需要走拦截器则调用
Response networkResponse = chain.proceed(networkRequest);
如果cacheResponse不为空并且networkResponse.code=304,则继续从缓存中读取
4).如果response中含有响应体body,并且缓存策略是可以被缓存,则可以cache.put(response)
}
(11).ConnectInterceptor:
streamAllocation在这里使用;
Response intercept(Chain chain){
/**
HttpCodec主要用来编码request以及解码response
/
1).HttpCodec httpCodec = streamAllocation.newStream();
/
RealConnection 进行实际的网络io操作的
**/
2).RealConnection connection = streamAllocation.connection();
3).return chain.proceed(httpCodec , connection );
}
streamAllocation.newStream()分析:
public HttpCodec newStream(){
RealConnection resultConnection = findHealthyConnection();
HttpCodec resultCodec = resultConnection.newStream();
}
private RealConnection findHealthyConnection(){
看当前connection能否复用
不能复用从连接池中获取新的connection ,
执行connection.connect();操作
并put到连接池中
}
PPT的总结:
1).弄一个RealConnection(实际的网络请求)
2).选择不同的链接方式(是否需要隧道连接和原始的socket连接)
3).调用下一个拦截器
connectionPool介绍:在时间限制内复用connection,同时还要对它进行有效的清理回收工作。
不管是http1.0或者http2.0的keep-alive机制或者多路复用机制,在实现上都需要引入一个连接池的概念,来维护我们整个http的网络连接;okhttp把客户端跟服务端的链接抽象成一个connection类,而realConnection是它的实现类;connectionPool用来管理这些connection,这些连接的复用;当共享相同的地址的时候就可以复用链接,同时connectionpool还实现了哪些链接可以保持打开状态以备后面使用的策略。
1).如何对连接池进行简单的get put操作:
总结:
a.每次一个http请求都会产生一个StreamAllocation对象(在重试重定向拦截器中初始化的)
b.将StreamAllocation对象的弱引用添加到RealConnection对象的allocations集合中(是ArrayList,能判断集合的大小,根据大小来判断该链接是否超过了最大的连接数)
c.从链接池中获取链接,复用
2).如何对connection回收:
利用了我们的GC回收算法,链接中StreamAllocation的数量会渐渐变为0,当它变为0的时候会被线程池监测到然后回收,这样就能保持多个健康的keep-alive连接;在connectionPool中有个独立的线程会开启cleanRunnable来清理我们的连接池
(12).CallServerInterceptor:
向服务器发送真正的网络请求,能读取服务端给我们的响应;
Response intercept(Chain chain){
httpCodec.writeRequestHeaders(request);
//向socket中写入我们的body信息
request.body().writeTo(bufferedRequestBody);
//表示我们整个网络工作的写入工作已完成
httpCodec.finishRequest();
//读取网络相应的头部信息
responseBuilder = httpCodec.readResponseHeaders();
httpCodec.openResponseBody(response);
}
(13).okhttp面试
1).socket:
基于应用层跟传输层中抽象出的一层;是一个对TCP/IP协议封装的API,不是协议,成对出现,一对套接字;TCP采取字节流方式来提供可靠的字节流服务的;UDP采用数据报文方式来提供数据,打包发送服务。
2).websocket
短轮询:每隔一段时间向服务端发送请求获取数据;缺点:某个时间段server没有更新数据,短轮询都是无效的,浪费资源
长轮询:向服务端发送请求,如果服务端数据没有变化会一直保持这个request直到数据更新,返回response。
http:请求响应协议
websocket:与HTTP同等的,基于tcp的双向通信协议;向服务端发送一个http请求/"Upgrade WebSocket";服务端解析这些附加的头信息产生response
Socket:不是一个协议,接口
okhttp支持websocket
3).http缓存
强制缓存
Expires:服务端返回的到期时间,http1.0使用,目前作用暂时失效;缺点:Expires是服务器时间,跟客户端时间有误差。
Cache-Control:服务器返回的response头部信息,告诉客户端是从本地还是服务器获取消息;有如下五个取值;private(客户端可以缓存),public(客户端和代理服务端都可以缓存),max-age(表示我们的缓存内容在多少秒后失效),no-cache(通过对比缓存来验证我们的数据,主要是表示强势缓存的标识无法处理我们的缓存),no-store(所有内容都不需要缓存)
对比缓存
首先需要进行比较判断是否可以使用缓存;服务端会将缓存标识与数据一起返给客户端。
ETag/If-None-Match
ETag:服务器响应请求时,告诉客户端当前资源在服务器的唯一标识
If-None-Match:再次请求服务器时,通过此字段通知服务器客户端缓存数据的唯一标识
示例:客户端发起请求->有缓存但过期->有Etag->向服务器请求中加If-None-Match->服务器决策->返回200或者304
Last-Modified/If-Modified-Since
Last-Modified:服务器在响应请求时,告诉客户端资源的最后修改时间。
If-Modified-Since:再次请求服务器时,通过此字段通知服务器上次请求时,服务器返回的资源最后修改时间。
Etag优先级高于Last-Modified。
在Etag不能判断的前提下,示例:客户端发起请求->有缓存但过期->有Last-Modified->向服务器请求中加If-Modified-Since->服务器决策->返回200或者304
4).断点续传原理
从文件已经下载完的地方继续下载
在request头部添加RANGE标识;例如RANGE:bytes=200080-;表示从字节20080开始下载
5).多线程下载
每个线程只负责下载文件的一部分
RandomAccessFile.setLength();//设置跟下载文件一样大小的文件。
开启多个线程,设置下载的起始跟结束位置,每个线程建立连接设置range:bytes=起始位置-结束位置;网络返回数据为206代表部分资源请求成功
6).文件上传
Content-Type属性指定请求和响应的HTTP内容类型
boundary:分隔符,用来分隔数据
7)https
https是一种基于SSL/TLS的Http协议
http缺点:所有传输内容都是明文
https:所有传输的内容都是经过加密
对称加密:是指加密和解密使用的密钥是一个key
不对称加密:加解密不是同一个key,有一个是公开的
对称加密所使用的密钥可以通过非对称加密发送过去
过程:请求https连接;返回证书(公钥);产生随机(对称)密钥,使用公钥对对称密钥加密,发送加密后的对称密钥,之后通过对称密钥加密的密文通信