OkHttp梳理
使用
默认GET、request.method()配置其它类型
//GET OkHttpClient client = new OkHttpClient.Builder() .build(); Request request = new Request.Builder() .url(Api.COMMON_LIST) .build(); Call call = client.newCall(request); call.enqueue(new Callback() {}); //POST OkHttpClient client = new OkHttpClient.Builder() .build(); FormBody formBody = new FormBody.Builder() .add("username", "10022000000") .add("password", "111111") .build(); Request request = new Request.Builder() .post(formBody) .url(Api.SIGN_IN) .build(); Call call = client.newCall(request); call.enqueue(new Callback() {}); 复制代码
源码
不用特意记它的流程,这些看源码都能找到的
OKHttpClient:配置:拦截器、代理、cookie...
Request:配置:url、method、headers...
RealCall:
包装成AsyncCall(--> Runnable)
线程池中执行
asyncCall.run --> asyncCall.execute中调用getResponseWithInterceptorChain
直接调用getResponseWithInterceptorChain
call.enqueue/execute:进行分发、处理listener等
execute()
enqueue(callback)
execute
//call.execute <!--#RealCall--> public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } transmitter.timeoutEnter(); transmitter.callStart(); try { client.dispatcher().executed(this); //--> Dispatcher#runningSyncCalls.add(call); //call并不会在拦截器中执行,是用来计数、判断等操作的 return getResponseWithInterceptorChain(); } finally { client.dispatcher().finished(this);// 从runningSyncCalls中移除 } } 复制代码
enqueue
异步时会有
Dispatcher# maxRequests = 64; maxRequestsPerHost = 5
限制
//call.enqueue <!--#RealCall--> public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } transmitter.callStart(); client.dispatcher().enqueue(new AsyncCall(responseCallback)); } <!--#Dispatcher--> void enqueue(AsyncCall call) { synchronized (this) { readyAsyncCalls.add(call); ... } promoteAndExecute(); //--> runningAsyncCalls.add(asyncCall); } private boolean promoteAndExecute() { assert (!Thread.holdsLock(this)); List<AsyncCall> executableCalls = new ArrayList<>(); boolean isRunning; synchronized (this) { for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall asyncCall = i.next(); if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity. if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity. i.remove(); asyncCall.callsPerHost().incrementAndGet(); executableCalls.add(asyncCall); runningAsyncCalls.add(asyncCall); } isRunning = runningCallsCount() > 0; } for (int i = 0, size = executableCalls.size(); i < size; i++) { AsyncCall asyncCall = executableCalls.get(i); asyncCall.executeOn(executorService()/**线程池*/); } return isRunning; } <!--#RealCall.AsyncCall--> final class AsyncCall extends NamedRunnable { void executeOn(ExecutorService executorService) { try { //放入线程池,子线程执行run方法调用下面的execute executorService.execute(this); } catch (RejectedExecutionException e) { responseCallback.onFailure(RealCall.this, ioException); } finally { client.dispatcher().finished(this); //从runningAsyncCalls中移除 } } protected void execute() { try { Response response = getResponseWithInterceptorChain(); responseCallback.onResponse(RealCall.this, response); } catch (IOException e) { responseCallback.onFailure(RealCall.this, e); } finally { client.dispatcher().finished(this); } } } 复制代码
拦截器
七种拦截器:
addInterceptor(Interceptor),
应用拦截器,开发者设置
,在所有的拦截器处理之前进行最早的拦截处理,比如一些公共参数,Header都可以在这里添加。RetryAndFollowUpInterceptor,对连接做一些初始化工作,以及请求失败的重试工作,重定向的后续请求工作。
BridgeInterceptor,为用户构建一个能够进行网络访问的请求(比如添加content-type、content-length、User-Agent、Host、Cookie、gzip等),同时后续工作将网络请求回来的响应转化为用户可用的Response,以及使用Cookie存取。
CacheInterceptor,处理cache相关处理,会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存,而且如果本地有了可⽤的Cache,就可以在没有网络交互的情况下就返回缓存结果。
ConnectInterceptor,负责建立连接了,会建立TCP连接或者TLS连接,以及负责编码解码的HttpCode。
networkInterceptors,
网络拦截器,开发者设置
,所以本质上和第一个拦截器差不多,但是由于位置不同,用处也不同。这个位置添加的拦截器可以看到请求和响应的数据了,所以可以做一些网络调试。CallServerInterceptor,进行网络数据的请求和响应了,也就是实际的网络I/O操作,通过socket读写数据。
应重桥缓连网请
Response getResponseWithInterceptorChain() throws IOException { List<Interceptor> interceptors = new ArrayList<>(); //自定义:应用程序拦截器 interceptors.addAll(client.interceptors()); //重连重定向拦截器 interceptors.add(new RetryAndFollowUpInterceptor(client)); //桥接拦截器 interceptors.add(new BridgeInterceptor(client.cookieJar())); //缓存拦截器 interceptors.add(new CacheInterceptor(client.internalCache())); //连接拦截器 interceptors.add(new ConnectInterceptor(client)); //自定义:网络拦截器 if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } //请求拦截器 interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); try { Response response = chain.proceed(originalRequest); return response; } catch (IOException e) { } finally { } } 复制代码
RetryAndFollowUpInterceptor
OkHttp原始解析(二)重定向
重连重定向拦截器:对连接做一些初始化工作,以及请求失败的重试工作,重定向的后续请求工作。
public Response intercept(Chain chain) throws IOException { Request request = chain.request(); RealInterceptorChain realChain = (RealInterceptorChain) chain; Transmitter transmitter = realChain.transmitter(); int followUpCount = 0; Response priorResponse = null; while (true) { transmitter.prepareToConnect(request); // 处理取消事件 if (transmitter.isCanceled()) { throw new IOException("Canceled"); } Response response; boolean success = false; try { response = realChain.proceed(request, transmitter, null); success = true; } catch (RouteException e) { // 判断是否满足重定向条件,满足则重试,不满足就抛异常 // The attempt to connect via a route failed. The request will not have been sent. if (!recover(e.getLastConnectException(), transmitter, false, request)) { throw e.getFirstConnectException(); } continue; } catch (IOException e) { // 与服务器连接失败,检查是否满足重定向条件,满足则重试,不满足就抛异常 // An attempt to communicate with a server failed. The request may have been sent. boolean requestSendStarted = !(e instanceof ConnectionShutdownException); if (!recover(e, transmitter, requestSendStarted, request)) throw e; continue; } finally { // 检测到其他未知异常,则释放连接和资源 // The network call threw an exception. Release any resources. if (!success) { transmitter.exchangeDoneDueToException(); } } // 绑定上一个Response,指定body为空 // Attach the prior response if it exists. Such responses never have a body. if (priorResponse != null) { response = response.newBuilder() .priorResponse(priorResponse.newBuilder() .body(null) .build()) .build(); } Exchange exchange = Internal.instance.exchange(response); Route route = exchange != null ? exchange.connection().route() : null; // 根据响应码处理请求,返回Request不为空时则进行重定向处理 Request followUp = followUpRequest(response, route); if (followUp == null) { if (exchange != null && exchange.isDuplex()) { transmitter.timeoutEarlyExit(); } return response; } RequestBody followUpBody = followUp.body(); if (followUpBody != null && followUpBody.isOneShot()) { return response; } closeQuietly(response.body()); if (transmitter.hasExchange()) { exchange.detachWithViolence(); } // 限制重定向次数最多为20,超过则抛出异常 if (++followUpCount > MAX_FOLLOW_UPS) { throw new ProtocolException("Too many follow-up requests: " + followUpCount); } request = followUp; priorResponse = response; } } 复制代码
1.1 重试
private boolean recover(IOException e, Transmitter transmitter, boolean requestSendStarted, Request userRequest) { // The application layer has forbidden retries. if (!client.retryOnConnectionFailure()) return false; // We can't send the request body again. if (requestSendStarted && requestIsOneShot(e, userRequest)) return false; // This exception is fatal. if (!isRecoverable(e, requestSendStarted)) return false; // No more routes to attempt. if (!transmitter.canRetry()) return false; // For failure recovery, use the same route selector with a new connection. return true; } 复制代码
1.2 资源重定向
客户端向服务器发送一个请求,获取对应的资源,服务器收到请求后,发现请求的这个资源实际放在另一个位置,于是服务器在返回的响应头的Location字段中写入那个请求资源的正确的URL,并设置reponse的状态码为30x 。
private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException { if (userResponse == null) throw new IllegalStateException(); int responseCode = userResponse.code(); final String method = userResponse.request().method(); switch (responseCode) { // 407 case HTTP_PROXY_AUTH: // 代理认证 Proxy selectedProxy = route != null ? route.proxy() : client.proxy(); if (selectedProxy.type() != Proxy.Type.HTTP) { throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy"); } return client.proxyAuthenticator().authenticate(route, userResponse); // 401 case HTTP_UNAUTHORIZED: // 身份认证 return client.authenticator().authenticate(route, userResponse); // 308 case HTTP_PERM_REDIRECT: // 307 case HTTP_TEMP_REDIRECT: // 如果是307、308 这两种状态码 // 不对GET、HEAD 以外的请求重定向 if (!method.equals("GET") && !method.equals("HEAD")) { return null; } // fall-through // 300 case HTTP_MULT_CHOICE: // 301 case HTTP_MOVED_PERM: // 302 case HTTP_MOVED_TEMP: // 303 case HTTP_SEE_OTHER: // 客户端关闭重定向 if (!client.followRedirects()) return null; // 从响应头中获取Location字段 String location = userResponse.header("Location"); if (location == null) return null; HttpUrl url = userResponse.request().url().resolve(location); // Don't follow redirects to unsupported protocols. if (url == null) return null; // If configured, don't follow redirects between SSL and non-SSL. boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme()); if (!sameScheme && !client.followSslRedirects()) return null; // 重建一个新的Request Request.Builder requestBuilder = userResponse.request().newBuilder(); if (HttpMethod.permitsRequestBody(method)) { final boolean maintainBody = HttpMethod.redirectsWithBody(method); if (HttpMethod.redirectsToGet(method)) { requestBuilder.method("GET", null); } else { RequestBody requestBody = maintainBody ? userResponse.request().body() : null; requestBuilder.method(method, requestBody); } if (!maintainBody) { requestBuilder.removeHeader("Transfer-Encoding"); requestBuilder.removeHeader("Content-Length"); requestBuilder.removeHeader("Content-Type"); } } // When redirecting across hosts, drop all authentication headers. This // is potentially annoying to the application layer since they have no // way to retain them. if (!sameConnection(userResponse.request().url(), url)) { requestBuilder.removeHeader("Authorization"); } return requestBuilder.url(url).build(); // 408 case HTTP_CLIENT_TIMEOUT: // 需要发送一次相同的请求 // ...... // 503 case HTTP_UNAVAILABLE: if (userResponse.priorResponse() != null && userResponse.priorResponse().code() == HTTP_UNAVAILABLE) { // We attempted to retry and got another timeout. Give up. return null; } if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) { // specifically received an instruction to retry without delay return userResponse.request(); } return null; default: return null; } } 复制代码
当重定向时,如果想要根据某个状态码或重定向地址,自定义操作,可以自定义拦截器:
路径重定向
http --> https重定向
Okhttp配置:禁止重定向或自定义重定向拦截器时使用
new OkHttpClient().newBuilder() .followRedirects(false) //禁制OkHttp的重定向操作,我们自己处理重定向 .followSslRedirects(false)//https的重定向也可处理 复制代码
自定义拦截器:
public class RedirectInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { okhttp3.Request request = chain.request(); Response response = chain.proceed(request); int code = response.code(); if (code == 307) { //获取重定向的地址 String location = response.headers().get("Location"); LogUtils.e("重定向地址:", "location = " + location); //重新构建请求 Request newRequest = request.newBuilder().url(location).build(); response = chain.proceed(newRequest); } return response; } } 复制代码
BridgeInterceptor
桥拦截器:为用户构建一个能够进行网络访问的请求(比如添加content-type、content-length、User-Agent、Host、Cookie、gzip等),同时后续工作将网络请求回来的响应转化为用户可用的Response,以及使用Cookie存取。
cookieJar默认使用CookieJar.NO_COOKIES,所以OKHttp默认不支持cookie
public final class BridgeInterceptor implements Interceptor { public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); RequestBody body = userRequest.body(); if (body != null) { MediaType contentType = body.contentType(); if (contentType != null) { requestBuilder.header("Content-Type", contentType.toString()); } long contentLength = body.contentLength(); if (contentLength != -1) { requestBuilder.header("Content-Length", Long.toString(contentLength)); requestBuilder.removeHeader("Transfer-Encoding"); } else { requestBuilder.header("Transfer-Encoding", "chunked"); requestBuilder.removeHeader("Content-Length"); } } if (userRequest.header("Host") == null) { requestBuilder.header("Host", hostHeader(userRequest.url(), false)); } if (userRequest.header("Connection") == null) { requestBuilder.header("Connection", "Keep-Alive"); } boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true; requestBuilder.header("Accept-Encoding", "gzip"); } // cookieJar默认使用CookieJar.NO_COOKIES,所以OKHttp默认不支持cookie List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url()); if (!cookies.isEmpty()) { //设置到请求头中 requestBuilder.header("Cookie", cookieHeader(cookies)); } if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", Version.userAgent()); } Response networkResponse = chain.proceed(requestBuilder.build()); //保存cookie HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) { GzipSource responseBody = new GzipSource(networkResponse.body().source()); Headers strippedHeaders = networkResponse.headers().newBuilder() .removeAll("Content-Encoding") .removeAll("Content-Length") .build(); responseBuilder.headers(strippedHeaders); String contentType = networkResponse.header("Content-Type"); responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody))); } return responseBuilder.build(); } } 复制代码
使用CookieJar
OkHttp3入门介绍之Cookie持久化
链接关闭后,服务端不会记录用户的信息,Cookie 的作用就是用于解决 "如何记录客户端的用户信息";
键值对:username=John Doe
请求时:自动携带,供服务器识别
返回时:更新cookie信息;
public interface CookieJar { /** 内部默认实现,不做任何操作. */ CookieJar NO_COOKIES = new CookieJar() { //返回结果时,缓存或者持久化cookie @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { } //请求时,获取对应的cookie @Override public List<Cookie> loadForRequest(HttpUrl url) { return Collections.emptyList(); } }; } 复制代码
Cookie本地化读写
实现CookieJar接口,设置到OkHttpClientBuilder。 Cookie存取方式:
本地存储:SP、文件
内存:map
CacheInterceptor
缓存拦截器:处理cache相关处理,会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存,而且如果本地有了可⽤的Cache,就可以在没有网络交互的情况下就返回缓存结果。
缓存机制
客户端和服务端配合,请求和返回中携带各种参数来判断缓存策略;
HTTP缓存机制及原理
不一样的HTTP缓存体验
客户端、服务器配合,设置参数Expires、Cache-Control、max-Age等参数;
不属于okhttp上的功能,只是通过http协议和DiskLruCache做了处理而已;
强制缓存
有缓存、有效-->用缓存
没有缓存、失效--> 请求新数据--> 数据放入本地
对比缓存
缓存标识-->服务器返回是否有效
有效--> 本地缓存
无效--> 返回新数据--> 数据放入本地
上传、下载
异步:onResponse、onFailed是在子线程工作的
如何判断是否联网、WIFI、4G、网速?
现在的Android源码网络实现,用的是OKhttp
maxRequest:64(最多64个请求)、maxRequestPerHost:5(每个主机最多5个),多的进队列等待。
启用Gzip
ConnectInterceptor
负责建立连接了,会建立TCP连接或者TLS连接,以及负责编码解码的HttpCode。
CallServerInterceptor
进行网络数据的请求和响应了,也就是实际的网络I/O操作,通过socket读写数据。
两种自定义拦截器区别
applications:
最开始调用:关注
最原始
的请求数据不会因为重定向、缓存等原因不执行
只会被调用一次,即使这个响应是从缓存中获取的
network:
已经建立链接,可以观察到
真实的请求和响应数据
当返回缓存时不被调用
自定义拦截器
请求
固定参数封装
请求数据加密
测试/正式url切换
header动态添加
返回数据
返回数据读取、封装
log日志打印
public class TestInterceptor implements Interceptor { private final Charset UTF8 = Charset.forName("UTF-8"); @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); /** * pre:对参数进行封装:校验参数、添加公有参数、打印参数url等信息... */ HttpUrl httpUrl = originalRequest.url() .newBuilder() .addQueryParameter("key","varmin") .build(); Request request = originalRequest.newBuilder().url(httpUrl).build(); for (String queryParameterName : request.url().queryParameterNames()) { Log.d(TAG, "intercept: paramName = " + queryParameterName); } Response response = chain.proceed(request); /** * after:对返回内容:长度、格式、打印等控制 */ ResponseBody responseBody = response.body(); BufferedSource source = responseBody.source(); source.request(Long.MAX_VALUE); Buffer buffer = source.getBuffer(); Charset charset = UTF8; MediaType contentType = responseBody.contentType(); if (contentType != null) { charset = contentType.charset(UTF8); } //clone:不会引起callback中responseBody的close Log.d(TAG, "intercept: response" + buffer.clone().readString(charset)); return response; } } 复制代码
OKio
Okio源码解析
Tips
response.body().string() 只能调用一次?
多次调用:java.lang.IllegalStateException: closed
使用buffer.clone方法获取内容
<!--再次读取是报错--> public long read(Buffer sink, long byteCount) throws IOException { //... if (closed) throw new IllegalStateException("closed"); return buffer.read(sink, toRead); } <!--读取以后close释放资源--> @Override public void close() throws IOException { if (closed) return; closed = true; source.close(); buffer.clear(); } <!--使用buffer.clone方法获取内容--> BufferedSource source = response.body().source(); Buffer buffer = source.getBuffer(); Charset charset = UTF8; MediaType contentType = responseBody.contentType(); if (contentType != null) { charset = contentType.charset(UTF8); } //clone:不会引起callback中responseBody的close Log.d(TAG, "intercept: response" +buffer.clone().readString(charset));
作者:Varmin_
链接:https://juejin.cn/post/7037782267485224996