This article is for the collation of the past notes, in this only for the record, do not do rigorous technical sharing.
OkHttp: OkHttp: OkHttp: OkHttp: OkHttp: OkHttp: OkHttp: OkHttp: OkHttp: OkHttp OKHTTP3 source code and Design Patterns (Part 2)
use
- Default GET, request.method() configures other types
//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() {});
Copy the code
The source code
Do not deliberately remember its process, these look at the source can be found
- OKHttpClient: Configuration: interceptors, proxies, cookies…
- Request:配置:url、method、headers…
- RealCall:
- Call. enqueue/execute: Distributes and processes listeners
- execute()
- Direct call getResponseWithInterceptorChain
- enqueue(callback)
- Wrapped AsyncCall(–> Runnable)
- Execute in thread pool
- AsyncCall. The run – > asyncCall. Execute call getResponseWithInterceptorChain
execute
//call.execute<! --#RealCall-->public Response execute(a) 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 is not executed in interceptors, but is used for counting, judging, and so on
return getResponseWithInterceptorChain();
} finally {
client.dispatcher().finished(this);// Remove from runningSyncCalls}}Copy the code
enqueue
- Asynchronously
Dispatcher# maxRequests = 64; maxRequestsPerHost = 5
limit
//call.enqueue<! --#RealCall-->public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.callStart();
client.dispatcher().enqueue(newAsyncCall(responseCallback)); } <! --#Dispatcher-->void enqueue(AsyncCall call) {
synchronized (this) { readyAsyncCalls.add(call); . } promoteAndExecute();//--> runningAsyncCalls.add(asyncCall);
}
private boolean promoteAndExecute(a) {
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()/** Thread pool */);
}
returnisRunning; } <! --#RealCall.AsyncCall-->final class AsyncCall extends NamedRunnable {
void executeOn(ExecutorService executorService) {
try {
// Put it into the thread pool, and the child thread executes the run method to call execute below
executorService.execute(this);
} catch (RejectedExecutionException e) {
responseCallback.onFailure(RealCall.this, ioException);
} finally {
client.dispatcher().finished(this); // Remove from runningAsyncCalls}}protected void execute(a) {
try {
Response response = getResponseWithInterceptorChain();
responseCallback.onResponse(RealCall.this, response);
} catch (IOException e) {
responseCallback.onFailure(RealCall.this, e);
} finally {
client.dispatcher().finished(this); }}}Copy the code
The interceptor
Seven interceptors:
- addInterceptor(Interceptor).
Apply interceptor, developer Settings
, the earliest interception processing before all interceptor processing, such as somePublic argument, HeaderAll can be added here. - RetryAndFollowUpInterceptor, do some of the connection in the initialization, and the failure to request retry, redirect the subsequent request.
- BridgeInterceptor builds a network access request for the User (adding content-Type, Content-Length, user-Agent, Host, Cookie, gzip, etc.) At the same time, the follow-up work will convert the Response returned by the network request into the Response available to the user, and use Cookie access.
- CacheInterceptor, which handles cache-related processing, caches the requested value based on the configuration of the OkHttpClient object and cache policy, and can return cached results without network interaction if a locally available cache is available.
- ConnectInterceptor, which is responsible for establishing a TCP connection or TLS connection, and the HttpCode that decodes it.
- networkInterceptors.
Web interceptor, developer Settings
, so it’s essentially the same as the first interceptor, butDepending on the location, the use is different. Interceptor added to this locationYou can now see the request and response data, so you can do some network debugging. - The CallServerInterceptor requests and responds to network data, that is, the actual NETWORK I/O operations. It reads and writes data through sockets.
Should be heavy bridge slow network please
Response getResponseWithInterceptorChain(a) throws IOException {
List<Interceptor> interceptors = new ArrayList<>();
// Custom: application interceptor
interceptors.addAll(client.interceptors());
// Rewire the redirect interceptor
interceptors.add(new RetryAndFollowUpInterceptor(client));
// Bridge interceptor
interceptors.add(new BridgeInterceptor(client.cookieJar()));
// Cache interceptor
interceptors.add(new CacheInterceptor(client.internalCache()));
// Connect interceptor
interceptors.add(new ConnectInterceptor(client));
// Custom: network interceptor
if(! forWebSocket) { interceptors.addAll(client.networkInterceptors()); }// Request interceptor
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{}}Copy the code
RetryAndFollowUpInterceptor
OkHttp raw parsing (ii) redirection
Reconnection redirection interceptor: Does some initialization of the connection, retries of failed requests, and redirects subsequent requests.
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);
// Handle cancel events
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
Response response;
boolean success = false;
try {
response = realChain.proceed(request, transmitter, null);
success = true;
} catch (RouteException e) {
// Check whether redirection conditions are met. If yes, try again. If no, throw an exception
// 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) {
If yes, try again. If no, throw an exception
// An attempt to communicate with a server failed. The request may have been sent.
booleanrequestSendStarted = ! (einstanceof ConnectionShutdownException);
if(! recover(e, transmitter, requestSendStarted, request))throw e;
continue;
} finally {
// If other unknown exceptions are detected, connections and resources are released
// The network call threw an exception. Release any resources.
if (!success) {
transmitter.exchangeDoneDueToException();
}
}
// bind a Response, specifying that the body is null
// 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;
// The Request is processed according to the response code
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();
}
// Limit the number of redirects to 20, exceeding which an exception will be thrown
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: "+ followUpCount); } request = followUp; priorResponse = response; }}Copy the code
1.1 try again
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;
}
Copy the code
1.2 Resource Redirection
The client sends a request to the server to obtain the corresponding resource. After receiving the request, the server finds that the requested resource is actually located in a different Location. The server writes the correct URL of the requested resource in the Location field of the returned response header and sets the status code of the Reponse to 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 authenticationProxy 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:
// Identity authentication
return client.authenticator().authenticate(route, userResponse);
/ / 308
case HTTP_PERM_REDIRECT:
/ / 307
case HTTP_TEMP_REDIRECT:
// If the status code is 307, 308
// Do not redirect requests other than GET and 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:
// The client closes the redirect
if(! client.followRedirects())return null;
// Get the Location field from the response header
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;
// Create a new 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:
// The same request needs to be sent once
/ /...
/ / 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; }}Copy the code
If you want to customize operations based on a status code or redirect address when redirecting, you can customize interceptors:
- Path redirection
- HTTP –> HTTPS redirection
Okhttp configuration: used when disabling redirection or custom redirection interceptors
new OkHttpClient().newBuilder()
.followRedirects(false) // Disable OkHttp redirects and handle redirects ourselves
.followSslRedirects(false)// HTTPS redirects can also be handled
Copy the code
Custom interceptors:
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) {
// Get the redirect address
String location = response.headers().get("Location");
LogUtils.e("Redirect address:"."location = " + location);
// Rebuild the request
Request newRequest = request.newBuilder().url(location).build();
response = chain.proceed(newRequest);
}
returnresponse; }}Copy the code
BridgeInterceptor
Bridge interceptor: Construct a request for users to access the network (such as adding content-Type, Content-Length, user-Agent, Host, Cookie, gzip, etc.), and convert the Response returned from the network request into a Response available to users in the follow-up work. And use Cookie access.
- CookieJar uses cookiejar.no_cookies by default, so OKHttp does not support cookies by default
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 uses cookiejar.no_cookies by default, so OKHttp does not support cookies by default
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if(! cookies.isEmpty()) {// Set to the request header
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") = =null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build());
/ / save cookies
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)));
}
returnresponseBuilder.build(); }}Copy the code
Using CookieJar
OkHttp3 introduction to Cookie persistence
After the link is closed, the server will not record the user’s information. The function of Cookie is to solve the problem of “how to record the user information of the client”.
- Key-value pair: username=John Doe
- Request: automatic carry, for server identification
- When returned: update cookie information;
public interface CookieJar {
/** Internal default implementation, do nothing. */
CookieJar NO_COOKIES = new CookieJar() {
// Cache or persist cookies when results are returned
@Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {}// When requested, the corresponding cookie is obtained
@Override public List<Cookie> loadForRequest(HttpUrl url) {
returnCollections.emptyList(); }}; }Copy the code
Cookie localization read and write
Implement the CookieJar interface and set it to OkHttpClientBuilder. Cookie access mode:
- Local storage: SP, file
- Memory: the map
CacheInterceptor
Cache interceptor: Handles cache-related processing, caches the request value according to the configuration of the OkHttpClient object and cache policy, and returns cached results without network interaction if a locally available cache is available.
Caching mechanisms
Client and server cooperate, request and return carry various parameters to judge the cache policy;
HTTP cache experience with different mechanisms and principles
- Set parameters such as Expires, cache-control, and max-age when the client and server cooperate.
- It is not a function of okHTTP, but is handled by HTTP and DiskLruCache.
Mandatory cache
- Cache available, valid –> use cache
- No caching, invalidation –> request new data –> Put data locally
Compared to the cache
Cache id –> Whether the server returns valid
-
Valid –> local cache
-
Invalid –> return new data –> put data locally
-
Upload, download
-
Asynchronous: onResponse and onFailed work on child threads
-
How to judge whether the Internet, WIFI, 4G, network speed?
-
The current Android source network implementation, using OKhttp
-
MaxRequest: 64 (a maximum of 64 requests), maxRequestPerHost: 5 (a maximum of 5 requests for each host).
-
To enable Gzip
ConnectInterceptor
Responsible for establishing a connection, will establish a TCP connection or TLS connection, and responsible for encoding and decoding the HttpCode.
CallServerInterceptor
Network data requests and responses, that is, actual network I/O operations, read and write data through sockets.
The two custom interceptors differ
- applications:
- First call: attention
The most primitive
Request data of - It does not fail to execute because of redirection, caching, etc
- Will only be called once, even if the response is fetched from the cache
- First call: attention
- The network:
- Link has been established and you can observe
Real request and response data
- Not called when the cache is returned
- Link has been established and you can observe
Custom interceptors
- request
- Fixed parameter package
- Request data encryption
- Test/official URL switching
- Header dynamic add
- Return the data
- Returns data read and encapsulated
- Log Printing
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: encapsulate parameters: verify parameters, add public parameters, print parameters 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: controls the length, format, print, etc. */
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: does not raise responseBody close in callback
Log.d(TAG, "intercept: response" + buffer.clone().readString(charset));
returnresponse; }}Copy the code
OKio
Okio source code analysis
Tips
Response.body ().string() can only be called once?
- Multiple calls: Java. Lang. An IllegalStateException: closed
- Use the buffer.clone method to get the content
<! Error: error: read again -->public long read(Buffer sink, long byteCount) throws IOException {
/ /...
if (closed) throw new IllegalStateException("closed");
returnbuffer.read(sink, toRead); } <! -- close release resource after read -->@Override
public void close(a) throws IOException {
if (closed) return;
closed = true; source.close(); buffer.clear(); } <! 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: does not raise responseBody close in callback
Log.d(TAG, "intercept: response" +buffer.clone().readString(charset));
Copy the code