OKHTTP相关文章虽然对,作为学习笔记使用,有问题希望能够尽情提出,共同交流
蒋八九
主要使用了建造者模式、责任链模式
OkHttpClient okHttpClient = new OkHttpClient(); //创建client对象
Request request = RequestBody.create(new MediaType(),jsonString);//post消息体
Request request = new Request.Builder().url(url).get().build(); //get请求
Request request = new Request.Builder().url(url).post(RequestBody.create(new MediaType(),jsonString)).build(); //post请求
Call call = okHttpClient.newCall(request); //获取call,实体是realCall
Response response = call.execute(); //同步阻塞
call.enqueue(new Callback()} //异步提交
OkhttpClient:
主要封装dispatch调度器、自定义拦截器、代理、协议、连接池、读写超时时间等;
Dispatch:
client构造中创建的。其中有一个线程池(cacheSchesualThreadPool线程池)、一个运行中的同步队列runningAsyncCalls、一个运行中异步队列runningSyncCalls、一个等待异步队列readyAsyncCalls,最大运行中异步任务数:64个 ;同一个host访问上限数5个。
public final class Dispatcher {
private int maxRequests = 64; 同时提交的请求任务数量上限
private int maxRequestsPerHost = 5; 同时请求同一个服务器数量
private @Nullable Runnable idleCallback;
private @Nullable ExecutorService executorService; 相当于cacheThreadPool线程池
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); 异步等待双向队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();异步运行双向队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); 同步运行双向队列
Request:
主要封装请求方式、请求头、请求地址
Call call = okHttpClient.newCall(request)——>return RealCall.newRealCall(this, request, false)
其实就是将client和request封装到realcall对象中
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket){
//realcall中同时创建了RetryAndFollowUpInterceptor对象
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call); 这个是http请求的监听器
return call;
}
提交任务
Response response = call.execute();
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
call.enqueue(new Callback()}
1、client.dispatcher().enqueue(new AsyncCall(responseCallback));
2、synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost){
runningAsyncCalls.add(call); 当前运行任务数小于64,服务器请求数小于5 入队
executorService().execute(call); 交给cacheThreadPoll线程池进行子线程操作
} else {
readyAsyncCalls.add(call); 入异步等待队列
}
}
3、最终在AyncCall类中的execute方法中执行拦截器方法
Response result = getResponseWithInterceptorChain();
一共有6个拦截器1、retryAndFollowUpInterceptor;2、BridgeInterceptor;3、CacheInterceptor;4、ConnectInterceptor;5、networkInterceptors;6、CallServerInterceptor;
首先创建一个StreamAllocation对象,然后将执行response = realChain.proceed(request, streamAllocation, null, null);将责任链+1然后分发到抛向下一个BridgeInterceptor
如果有自定义的请求头head或者是mediaType会将其添加进去,如果没有的话,会自动补充完成,如(“Host”, “www.baidu.com”)(“Connection”, “Keep-Alive”)(“Location”, “重定向地址”)(“Accept-Encoding”, “gzip”)(“User-Agent”, userAgent)
客户端缓存就是为了下次请求时节省请求时间,可以更快的展示数据
//配置缓存的路径,和缓存空间的大小;这里用的是diskLruCache缓存机制
Cache cache = new Cache(new File("/Users/zeal/Desktop/temp"),10*10*1024);
Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
从这可以看出底层用的是DiskLruCache缓存机制;
OkHttpClient okHttpClient = new OkHttpClient.Builder() .cache(cache).build();
CacheControl.Builder builder = new CacheControl.Builder();
builder.noCache();//不使用缓存,全部走网络
builder.noStore();//不使用缓存,也不存储缓存
builder.onlyIfCached();//只使用缓存
builder.noTransform();//禁止转码
builder.maxAge(10, TimeUnit.MILLISECONDS);//指示客户机可以接收生存期不大于指定时间的响应。
builder.maxStale(10, TimeUnit.SECONDS);//指示客户机可以接收超出超时期间的响应消息
builder.minFresh(10, TimeUnit.SECONDS);//指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
CacheControl cache = builder.build();//cacheControl
Request request = new Request.Builder().url(url).cacheControl(builder).build();
自定义拦截器应用:
OkHttpClient.Builder newBuilder = mOkHttpClient.newBuilder();
newBuilder.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
boolean connected = NetworkUtil.isConnected(context);
if (!connected) {
request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
}
Response response = chain.proceed(request);
return response;
}
});
连接拦截器主要工作就是找到一个可用的连接去连接服务器。主要要求对socket、http1、http2、https的理解
实现流程如下:
在拦截器中主要做了两件事:
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
在newStream中主要做了三件事:
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
noNewStreams();
在findHealthyConnection的流程:
1.上一个连接是否完好,是,直接使用
2.缓存中有没有可以用的,有,使用
3.自己创建
4.握手、连接
releasedConnection = this.connection; //先复用上一次连接
if (this.connection != null) {
result = this.connection;
}
if (result == null) { 如果本地没有,那么就连接池connectionPool中获取一个
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
result = connection;
} else {selectedRoute = route;} } }
..........
if (result != null) {
return result; 获取到了就return
}
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
routeSelection = routeSelector.next(); 如果连接池中也没有,就从路由选择器中获取
}
synchronized (connectionPool) {
List<Route> routes = routeSelection.getAll();
遍历routes
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
result = connection; 如果获取到了就返回
}
if (!foundPooledConnection) { 到最后都没有找到的话就自己new一个
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);
}}
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener); 连接并握手
routeDatabase().connected(result.route());
synchronized (connectionPool) {
Internal.instance.put(connectionPool, result); 最后将result添加到连接池中,方便下次复用
}
上面的result.connect()中
while (true) {
if (route.requiresTunnel()) { 如果是通道,则建立通道
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
} else { 否正建立socket,就是最简单的那中建立
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
建立http连接
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
break;
}
Http发送网络请求前两个步骤是:
httpCodec.writeRequestHeaders(request); 首先向web服务端发送请求命令;通过sink写
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) { 对Expect请求头做了处理
}
if (responseBuilder == null) { 如果是Expect请求头,把body写出去
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);}}
客户端向服务端发送的部分就结束了,剩下的就是送服务端读取了
responseBuilder = httpCodec.readResponseHeaders(false); 通过readResponseHeaders读取响应头
Response response = responseBuilder 构建responseBuilder对象
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
最后通过openResponseBody方法将Socket的输入流InputStream对象交给OkIo的Source对象BufferedSource中,最后封装到RealResponseBody中返回
return new RealResponseBody(contentType, -1L, Okio.buffer(source));
最后在ResponseBody中提供了string()方法读取字符串
response.body().string()
public final String string() throws IOException {
BufferedSource source = source(); 获取source输入流
Charset charset = Util.bomAwareCharset(source, charset()); 获取字符编码utf-8
return source.readString(charset); 将输入流转utf-8字符串输出
}