use

1. Add dependencies

Implementation 'com. Squareup. Okhttp3: okhttp: 3.14.9'Copy the code

2. Common request methods

1. Synchronize GET requests

  • Execution of the request is blocked until the HTTP response is returned
Create the OkHttpClient object
  1. Directly to create
val client = OkHttpClient()
Copy the code
  1. Create from Builder mode
val client = OkHttpClient.Builder()
    .build()
Copy the code
2. Create a Request object
val request = Request.Builder()
    .url("https://www.baidu.com")
    .get()
    .build()
Copy the code
3. Encapsulate request as a Call object
val call = client.newCall(request)
Copy the code
4. Call Call. execute to send the synchronization request
val response = call.execute() if (response.isSuccessful) { log(response.body()? .string()) } else { log(IOException("Unexpected code $response").message) }Copy the code
  • Note: After the child thread calls and sends the request, the current thread blocks until it receives the response
lifecycleScope.launch {
    withContext(Dispatchers.IO) {
        getSync()
    }
}
Copy the code
  • Don’t forget to add network request permissions
<uses-permission android:name="android.permission.INTERNET" />
Copy the code
  • If is HTTPS requests, might be an error: java.net.UnknownServiceException: CLEARTEXT communication to…
  • On Android P devices, if an application uses HTTP network requests with unencrypted plaintext traffic, the application cannot make network requests.

HTTPS is not affected. Similarly, if an application has a nested webView, the webView can only use HTTPS requests.

  • Need to solve the exception to the HTTPS requests, or in the AndroidManifest. The Application of XML file TAB to join the android: usesCleartextTraffic = “true”

2. Asynchronous GET requests

  • The execution of the request is non-blocking and the result of the execution is notified to the caller via an interface callback
  • The first three steps are the same, and the fourth step uses the asynchronous call.enqueue method
val client = OkHttpClient() val request = Request.Builder() .url("https://www.baidu.com") .get() .build() val call = Enqueue call.enqueue(object: Callback {override fun onFailure(call: call, e: override fun onFailure(call: call, e: call) IOException) { log("onFailure:${e.message}") runOnUiThread { tv.text = e.message } } override fun onResponse(call: Call, response: Response) { val result = response.body()?.string() log("onResponse:${result}") runOnUiThread { tv.text = "onResponse${result}" } } })Copy the code
  • Note: the callback method onResponse, onFailure is executed in a child/worker thread, so runOnUiThread is used in onResponse to update the UI;

3. The asynchronous POST request submits key/value pairs

  • There is an extra step to create the FormBody as the argument to the POST request
Val FormBody = formBody.builder ().add("k", "wanAndroid") .build() val request = Request.Builder() .url("https://www.wanandroid.com/article/query/0/json") .post(formBody) .build() val call = client.newCall(request) call.enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { log("onFailure:${e.message}") runOnUiThread { tv.text = e.message } } override fun onResponse(call: Call, response: Response) { val result = response.body()?.string() log("onResponse:${result}") runOnUiThread { tv.text = "onResponse${result}" } } })Copy the code

4. Post communication (uploading files)

Private fun postFile() {val client = OkHttpClient() val file= file (externalCacheDir,"ljy.txt") Parse ("text/x-markdown; "); // create RequestBody: val RequestBody = requestbody. create(MediaType. charset=utf-8"), file ) val request=Request.Builder() .url("https://api.github.com/markdown/raw") .post(requestBody) .build() client.newCall(request).enqueue(object : Callback{ override fun onFailure(call: Call, e: IOException) { log("onFailure:${e.message}") } override fun onResponse(call: Call, response: Response) { log("onResponse:${ response.body()?.string()}") } }) }Copy the code
  • You need to add read and write permissions to androidmanifest.xml, as well as runtime permission requests
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ! = PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) ! = PackageManager.PERMISSION_GRANTED ) { [email protected](arrayOf( Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE), 10001) } else { ... } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == 10001) { ... }}Copy the code

5. Download files asynchronously

private fun downloadFile() { val client = OkHttpClient() val url = "https://pic3.zhimg.com/v2-dc32dcddfd7e78e56cc4b6f689a24979_xl.jpg" val request = Request.Builder() .url(url) .build() client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { log("onFailure:${e.message}") } override fun onResponse(call: Call, response: Response) { val inputStream = response.body()?.byteStream() val fileOutputStream = FileOutputStream(File(externalCacheDir, "ljy.jpg")) val buffer = ByteArray(2048) var len: Int while (inputStream?.read(buffer).also { len = it ?: -1 } ! Fileoutputstream.write (buffer, 0, len)} fileOutputStream.flush() log(" File downloaded successfully ")}})}Copy the code

6. Post to submit the form

  • Sometimes other types of fields need to be uploaded as well
private fun sendMultipart() { val client = OkHttpClient() val file = File(externalCacheDir, "ljy.jpg") val requestBody: RequestBody = MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("name", "ljy") .addFormDataPart("age", "18") .addFormDataPart( "image", "header.jpg", RequestBody.create(MediaType.parse("image/png"), file) ) .build() val request: Request = Request.Builder() .header("Authorization", "Client-ID " + "..." ) .url("https://api.imgur.com/3/image") .post(requestBody) .build() client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { log("onFailure:${e.message}") } override fun onResponse(call: Call, response: Response) { log("onResponse:${response.body()?.string()}") } }) }Copy the code

Commonly used Settings

1. Set the timeout period

val client = OkHttpClient.Builder()
    .connectTimeout(30,TimeUnit.SECONDS)
    .readTimeout(60,TimeUnit.SECONDS)
    .writeTimeout(90,TimeUnit.SECONDS)
    .build()
Copy the code

2. Set the cache

// Set the cache path and size, Val client = okHttpClient.builder ().addNetworkInterceptor(CacheInterceptor()).cache(cache(File(cacheDir, "HttpCache2 "), 100 * 1024 * 1024L)).build() Interceptor { override fun intercept(chain: Interceptor.Chain): Response { var request: Request = chain.request() val var10000: Response val response: Response if (NetUtil isNetworkAvailable (this @ OkHttpDemoActivity)) {/ / if there is a net, return a valid for 30 Response, Response = chain.proceed(request) // Build a maxAge = 30 seconds CacheControl val CacheControl = CacheControl.Builder() .maxAge(30, TimeUnit.SECONDS) .build() .toString() var10000 = response.newBuilder() .removeHeader("Pragma") .removeHeader(" cache-control ") // Add 30 seconds of cachecontrol.header (" cache-control ", cachecontrol.build ()} else {// If there is no network, Request = request.newBuilder().cachecontrol (cachecontrol.force_cache).build() var10000 = chain.proceed(request) } return var10000 } }Copy the code
  • The cache constructor for okHttpClient. cache is as follows:
public Cache(File directory, long maxSize) {
    this(directory, maxSize, FileSystem.SYSTEM);
}

Cache(File directory, long maxSize, FileSystem fileSystem) {
    this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
Copy the code
  • As you can see DiskLruCache is also used;

3. If the configuration fails, try again

val client = OkHttpClient.Builder()
    .retryOnConnectionFailure(true)
    .build()
Copy the code

4. Persistent cookies

/ / add third-party libraries depend on the implementation 'com. Zhy: okhttputils: 2.6.2 / / persistent cookie, to keep the session session:  val cookieJar = new CookieJarImpl(new PersistentCookieStore(CommonModule.getAppContext())) val client = OkHttpClient.Builder() .cookieJar(cookieJar) .build()Copy the code

The source code parsing

Request

  1. Request.builder () is constructed as follows. Method defaults to GET
public Builder() { this.method = "GET"; this.headers = new Headers.Builder(); } public Request build() { if (url == null) throw new IllegalStateException("url == null"); return new Request(this); } Request(Builder Builder) {this.url = builder.url; this.method = builder.method; this.headers = builder.headers.build(); this.body = builder.body; this.tags = Util.immutableMap(builder.tags); }Copy the code
  1. The Request.BUilder post method is as follows:
public Builder post(RequestBody body) { return method("POST", body); } public Builder method(String method, @Nullable RequestBody body) { if (method == null) throw new NullPointerException("method == null"); if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0"); if (body ! = null && ! HttpMethod.permitsRequestBody(method)) { throw new IllegalArgumentException("method " + method + " must not have a request body."); } if (body == null && HttpMethod.requiresRequestBody(method)) { throw new IllegalArgumentException("method " + method + " must have a request body."); } this.method = method; this.body = body; return this; }Copy the code

OkHttpClient

  1. The OkHttpClient constructor is implemented as follows:
public OkHttpClient() { this(new Builder()); } public builder () {dispatcher = new dispatcher (); protocols = DEFAULT_PROTOCOLS; connectionSpecs = DEFAULT_CONNECTION_SPECS; eventListenerFactory = EventListener.factory(EventListener.NONE); proxySelector = ProxySelector.getDefault(); if (proxySelector == null) { proxySelector = new NullProxySelector(); } cookieJar = CookieJar.NO_COOKIES; socketFactory = SocketFactory.getDefault(); hostnameVerifier = OkHostnameVerifier.INSTANCE; certificatePinner = CertificatePinner.DEFAULT; proxyAuthenticator = Authenticator.NONE; authenticator = Authenticator.NONE; connectionPool = new ConnectionPool(); dns = Dns.SYSTEM; followSslRedirects = true; followRedirects = true; retryOnConnectionFailure = true; callTimeout = 0; connectTimeout = 10_000; readTimeout = 10_000; writeTimeout = 10_000; pingInterval = 0; } public OkHttpClient build() { return new OkHttpClient(this); }Copy the code
  1. OkHttpClient.newCall
@Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */);  } // Its internal call realCall.newRealCall:  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { // Safely publish the Call instance to the EventListener. RealCall call = new RealCall(client, originalRequest, forWebSocket); call.transmitter = new Transmitter(client, call); return call; } // The RealCall constructor is as follows:  private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; }Copy the code

Call

  1. Call.execute synchronous request method source code
@override public Response execute() throws IOException {// Determine whether a Call is executed, Synchronized (this) {if (executed) throw new IllegalStateException("Already executed "); synchronized (this) {if (executed) throw new IllegalStateException("Already executed "); executed = true; } transmitter.timeoutEnter(); transmitter.callStart(); Executed (this); // Call the dispatcher to add the request to the synchronous request queue. / / by the interceptor chain to obtain the response return getResponseWithInterceptorChain (); } finally {// Reclaim synchronization request client.dispatcher().finished(this); }}Copy the code
  1. Call.enqueue asynchronous request method source code
//RealCall implementation: @override public void enqueue(Callback responseCallback) {synchronized (this) {if (executed) throw new ensure that the call is executed only once IllegalStateException("Already Executed"); executed = true; } transmitter.callStart(); client.dispatcher().enqueue(new AsyncCall(responseCallback)); }Copy the code
  • You can see they both called the Dispatcher method
Dispatcher task scheduling
  • To control concurrent requests, the following variables are maintained
/** private int maxRequests = 64; /** private int maxRequestsPerHost = 5; /** Consumer thread pool */ private ExecutorService; Private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); Private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); Private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();Copy the code
  • The dispatcher executed methods are as follows:
Synchronized void executed(RealCall call) {// To add a request to a synchronized request queue runningSynccalls.add (call); synchronized void executed(RealCall call) {// To add a request to a synchronized request queue }Copy the code
  • Dispatcher ().finished Is used to collect synchronization requests as follows:
void finished(RealCall call) { finished(runningSyncCalls, call); } private <T> void finished(Deque<T> calls, T call) { Runnable idleCallback; Synchronized (this) {// remove synchronization request if (! calls.remove(call)) throw new AssertionError("Call wasn't in-flight!" ); idleCallback = this.idleCallback; } boolean isRunning = promoteAndExecute(); if (! isRunning && idleCallback ! = null) { idleCallback.run(); }}Copy the code
  • The dispatcher enqueue is as follows:
Void enqueue(AsyncCall call) {synchronized (this) {// Add the request to the ready asynchronous request queue readyAsynccalls.add (call); // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to // the same host. if (! Call.get ().forwebsocket) {// find existingCall through host AsyncCall existingCall = findExistingCallWithHost(call.host()); CallsPerHost if (existingCall! = null) call.reuseCallsPerHostFrom(existingCall); } } promoteAndExecute(); }Copy the code
  • The AsyncCall input is the inner class of RealCall, and the constructor input is the callback we passed in, and callback is called in the execute method, and execute is called in NamedRunnable’s run
final class AsyncCall extends NamedRunnable { ... void executeOn(ExecutorService executorService) { assert (! Thread.holdsLock(client.dispatcher())); boolean success = false; try { executorService.execute(this); success = true; } catch (RejectedExecutionException e) { InterruptedIOException ioException = new InterruptedIOException("executor rejected"); ioException.initCause(e); transmitter.noMoreExchanges(ioException); responseCallback.onFailure(RealCall.this, ioException); } finally { if (! success) { client.dispatcher().finished(this); // This call is no longer running! } } } @Override protected void execute() { boolean signalledCallback = false; transmitter.timeoutEnter(); try { Response response = getResponseWithInterceptorChain(); signalledCallback = true; responseCallback.onResponse(RealCall.this, response); } catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { responseCallback.onFailure(RealCall.this, e); } } catch (Throwable t) { cancel(); if (! signalledCallback) { IOException canceledException = new IOException("canceled due to " + t); canceledException.addSuppressed(t); responseCallback.onFailure(RealCall.this, canceledException); } throw t; } finally { client.dispatcher().finished(this); } } } public abstract class NamedRunnable implements Runnable { protected final String name; public NamedRunnable(String format, Object... args) { this.name = Util.format(format, args); } @Override public final void run() { String oldName = Thread.currentThread().getName(); Thread.currentThread().setName(name); try { execute(); } finally { Thread.currentThread().setName(oldName); } } protected abstract void execute(); }Copy the code
  • In the AsyncCall execute above, finished is also called in finally to reclaim asynchronous requests
void finished(AsyncCall call) {
    call.callsPerHost().decrementAndGet();
    finished(runningAsyncCalls, call);
}
Copy the code
  • The promoteAndExecute method is invoked in both Finished and asynchronous, which is implemented as follows
private boolean promoteAndExecute() { assert (! Thread.holdsLock(this)); List<AsyncCall> executableCalls = new ArrayList<>(); // 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); } // recalculate the number of synchronous asynchronous requests to execute isRunning = runningCallsCount() > 0; ExecutableCalls. Size (); executableCalls (); executableCalls. i < size; i++) { AsyncCall asyncCall = executableCalls.get(i); asyncCall.executeOn(executorService()); } return isRunning; } public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; } public synchronized int runningCallsCount() { return runningAsyncCalls.size() + runningSyncCalls.size(); }Copy the code
Call order for asynchronous requests:
  1. The consumer calls call.enqueue (Callback);
  2. Enqueue (new AsyncCall(responseCallback));
  3. Dispatcher (). Enqueue calls promoteAndExecute;
  4. ReadyAsyncCalls are iterated in promoteAndExecute, placed into executableCalls and runningAsyncCalls, and runningCallsCount is called to recalculate the number of synchronous asynchronous requests to execute. ExecutableCalls, calling asynccall.executeon (executorService());
  5. Executorservice. execute(this), where this is a runnable asyncCall, will call its run method.
  6. Execute is called in NamedRunnable’s run method and implemented in asyncCall.
  7. AsyncCall. Execute call the Response in the Response = getResponseWithInterceptorChain (), and call the callback, the final call the dispatcher () finished;
  8. Dispatcher ().finished calls the promoteAndExecute method until all requests in the queue are completed.

The interceptor chain

  • Interceptor is a powerful mechanism in OKHTTP, which can implement network listening, request and response rewriting, and request failure retry.
  • The synchronous request above have call getResponseWithInterceptorChain method in the asynchronous request source, the code is as follows
The Response getResponseWithInterceptorChain () throws IOException {/ / Build a full stack of interceptors. / / create a series of interceptors, List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); / / 1. Retry and failure to redirect interceptor interceptors. Add (new RetryAndFollowUpInterceptor (client)); // interceptors.add(new BridgeInterceptor(client.cookiejar ())); Add (new CacheInterceptor(client.internalCache())); Add (new ConnectInterceptor(client)); if (! forWebSocket) { interceptors.addAll(client.networkInterceptors()); Add (new CallServerInterceptor(forWebSocket)); // create interceptor chain, Chain.proceed = new RealInterceptorChain(Interceptors, transmitter, null, 0); originalRequest, this, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); boolean calledNoMoreExchanges = false; try { Response response = chain.proceed(originalRequest); if (transmitter.isCanceled()) { closeQuietly(response); throw new IOException("Canceled"); } return response; } catch (IOException e) { calledNoMoreExchanges = true; throw transmitter.noMoreExchanges(e); } finally { if (! calledNoMoreExchanges) { transmitter.noMoreExchanges(null); }}}Copy the code
  • Create a list of interceptors, place them in a list, create a chain of interceptors RealInterceptorChain, and execute the chain.proceed method
  • The PROCEED method is implemented as follows:
@Override public Response proceed(Request request) throws IOException { return proceed(request, transmitter, exchange); } public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange) throws IOException { ... // Call the next interceptor in the chain. RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange, index + 1, request, call, connectTimeout, readTimeout, writeTimeout); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next); . return response; }Copy the code
  • Get (index) to get the current interceptor, and execute the interceptor.intercept method to get a response.
  • GetResponseWithInterceptorChain incoming in the index is 0, then the current interceptor is RetryAndFollowUpInterceptor, so let’s take a look at his intercept method is how to implement
RetryAndFollowUpInterceptor
  • RetryAndFollowUpInterceptor intercept method code is as follows
@Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); RealInterceptorChain = (RealInterceptorChain) chain; RealInterceptorChain = (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; 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; // When IOException or RouteException occurs, execute the recover method} 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(); } } // 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 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(); If (++followUpCount > MAX_FOLLOW_UPS) {throw new ProtocolException("Too many follow-up requests: " + followUpCount); } request = followUp; priorResponse = response; }}Copy the code
  • The recover method code is as follows
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
  • RetryAndFollowUpInterceptor intercept method calls the next interceptor chain proceed method to obtain the response, and in the while (true) cycle according to the result of abnormal or response results to determine whether to request, For example, when IOException or RouteException occurs, the Recover method is executed, and ++followUpCount > MAX_FOLLOW_UPS determines the maximum retry times. If the followUpCount exceeds the maximum retry times, the loop will be directly interrupted.
  • By RealInterceptorChain. Proceed it will continue to call the next interceptor intercept method, by a getResponseWithInterceptorChain in order the next interceptor is BridgeInterceptor
  • So let’s continue with the BridgeInterceptor Intercept method
BridgeInterceptor
  • The BridgeInterceptor intercept method is as follows
@Override public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); RequestBody = 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"); } // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing // the transfer stream. boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true; requestBuilder.header("Accept-Encoding", "gzip"); } 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()); Return networkResponse = chain.proceed(requestBuilder.build()); / / added response headers 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)) {//Response.body input stream is GzipSource, 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(); }Copy the code
  • In the BridgeInterceptor Intercept, various judgements supplement the RequestBody’s request header and convert it into a request that can be accessed on the network. Then call the proceed method of the next interceptor chain to get the response, and supplement the respone response header by setting the cookieJar, extracting the gzip, and converting the requested response into a user-usable response.
  • A call to proceed of the next interceptor chain calls the Intercept method of the next interceptor, which is called CacheInterceptor
CacheInterceptor
  • The CacheInterceptor intercept method is as follows
@Override public Response Intercept (Chain Chain) throws IOException {// Try to obtain the cache Response Response cacheCandidate = cache ! = null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); //CacheStrategy maintains networkRequest and cacheResponse cache policies based on time CacheStrategy Strategy = new cacheStrategy.factory (now, chain-.Request (), cacheCandidate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse; if (cache ! = null) { cache.trackResponse(strategy); } if (cacheCandidate ! = null && cacheResponse == null) { closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.} Response // If we're forbidden from using the network and the cache is insufficient fail. if (networkRequest == null && cacheResponse == null) { return new Response.Builder() .request(chain.request()) .protocol(Protocol.HTTP_1_1) .code(504) .message("Unsatisfiable Request (only-if-cached)") .body(Util.EMPTY_RESPONSE) .sentRequestAtMillis(-1L) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); } // If network access is not required, If we don't need the network, we're done. if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } // Proceed of the next interceptor chain gets response Response networkResponse = null; try { networkResponse = chain.proceed(networkRequest); } finally { // If we're crashing on I/O or otherwise, don't leak the cache body. if (networkResponse == null && cacheCandidate ! = null) { closeQuietly(cacheCandidate.body()); } // If we have a cacheResponse too, then we're doing a conditional get. If (cacheResponse! = null) {// server returns 304, Response if (networkResponse.code() == HTTP_NOT_MODIFIED) {response Response = cacheresponse.newBuilder () .headers(combine(cacheResponse.headers(), networkResponse.headers())) .sentRequestAtMillis(networkResponse.sentRequestAtMillis()) .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); networkResponse.body().close(); // Update the cache after combining headers but before stripping the // Content-Encoding header (as performed by initContentStream()). cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); // Update the cache if (cache! = null) { if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { // Offer this request to the cache. CacheRequest cacheRequest = cache.put(response); return cacheWritingResponse(cacheRequest, response); } / / if not get request is to remove the cache if (HttpMethod. InvalidatesCache (networkRequest. Method ())) {try {cache. Remove (networkRequest); } catch (IOException ignored) { // The cache cannot be written. } } } return response; }Copy the code
  • The Intercept of a CacheInterceptor makes various judgments about whether the cache is used and whether the cache is updated. If a network request is made, the proceed method of the next interceptor chain is called to get a response.
  • The next interceptor is the ConnectInterceptor
ConnectInterceptor
  • The ConnectInterceptor intercept method is as follows: officially opens the OKHTTP network request
@Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); Transmitter transmitter = realChain.transmitter(); // We need the network to satisfy this request. Possibly for validating a conditional GET. boolean doExtensiveHealthChecks = ! request.method().equals("GET"); Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks); return realChain.proceed(request, transmitter, exchange); }Copy the code
  • NewExchange is called to obtain Exchange, and proceed of the next interceptor chain is called to pass to the next interceptor to obtain response. The newExchange method is as follows
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) { ... ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks); Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec); . }Copy the code
  • We called ExchangeFinder.find to get ExchangeCodec, where we get RealConnection by findHealthyConnection and return realConnection.newCode
public ExchangeCodec find(OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) { ... RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks); return resultConnection.newCodec(client, chain); . }Copy the code
  • FindHealthyConnection calls the findConnection method. The code for the findConnection method is as follows, where RealConnection is obtained by connection pool or New RealConnection, And called its connect method
  • The source code is long, and the key steps are listed below
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException { boolean foundPooledConnection = false; RealConnection result = null; . If (result == null) {// Attempt to get a connection from the pool.if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) { foundPooledConnection = true; result = transmitter.connection; }... }... If (result! = null) { // If we found an already-allocated or pooled connection, we're done. return result; }... Result = new RealConnection(connectionPool, selectedRoute); // Do TCP + TLS handshakes. This is a blocking operation. result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener); / / added to the connection pool connectionPool. RouteDatabase. Connected (result) route ()); . return result; }Copy the code
CallServerInterceptor
  • Finally, take a look at the Intercept of CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Exchange exchange = realChain.exchange(); Request request = realChain.request(); long sentRequestMillis = System.currentTimeMillis(); / / to write request header information exchange in the socket. The writeRequestHeaders (request); boolean responseHeadersStarted = false; Response.Builder responseBuilder = null; if (HttpMethod.permitsRequestBody(request.method()) && request.body() ! = null) { // If there's a "Expect: 100-continue" header on the request, Wait for a "HTTP/1.1 100 // Continue" response before transmitting the request body. If we don't get that, return // what we did get (such as a 4xx response) without ever transmitting the request body. if ("100-continue".equalsIgnoreCase(request.header("Expect"))) { exchange.flushRequest(); responseHeadersStarted = true; exchange.responseHeadersStart(); responseBuilder = exchange.readResponseHeaders(true); } if (responseBuilder == null) { if (request.body().isDuplex()) { // Prepare a duplex body so that the application can send a request body later. exchange.flushRequest(); BufferedSink bufferedRequestBody = Okio.buffer( exchange.createRequestBody(request, true)); // Write the body message request.body().writeto (bufferedRequestBody); } else { // Write the request body if the "Expect: 100-continue" expectation was met. BufferedSink bufferedRequestBody = Okio.buffer( exchange.createRequestBody(request, false)); request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); } } else { exchange.noRequestBody(); if (! exchange.connection().isMultiplexed()) { // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection // from being reused. Otherwise we're still obligated to transmit the request body to // leave the connection in a consistent state. exchange.noNewExchangesOnConnection(); } } } else { exchange.noRequestBody(); } / / request to end the if (the request body () = = null | |! request.body().isDuplex()) { exchange.finishRequest(); } if (! responseHeadersStarted) { exchange.responseHeadersStart(); } / / read response headers if (responseBuilder = = null) {responseBuilder = exchange. ReadResponseHeaders (false); } Response response = responseBuilder .request(request) .handshake(exchange.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); int code = response.code(); if (code == 100) { // server sent a 100-continue even though we did not request one. // try again to read the actual response response = exchange.readResponseHeaders(false) .request(request) .handshake(exchange.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); code = response.code(); } exchange.responseHeadersEnd(response); If (forWebSocket &&code == 101) {if (forWebSocket &&code == 101) { but we need to ensure interceptors see a non-null response body. response = response.newBuilder() .body(Util.EMPTY_RESPONSE) .build(); } else { response = response.newBuilder() .body(exchange.openResponseBody(response)) .build(); } if ("close".equalsIgnoreCase(response.request().header("Connection")) || "close".equalsIgnoreCase(response.header("Connection"))) { exchange.noNewExchangesOnConnection(); } if ((code == 204 || code == 205) && response.body().contentLength() > 0) { throw new ProtocolException( "HTTP " + code  + " had non-zero Content-Length: " + response.body().contentLength()); } return response; }Copy the code

My name is Jinyang. If you want to learn more about jinyang, please pay attention to the wechat public number “Jinyang said” to receive my latest articles