Network library selection

Theoretically speaking, almost all network libraries are based on Socket. On the basis of Socket, various application layer communication protocols can be realized, such as HTTP and FTP. Java network libraries can theoretically be used on the Android side, but due to the characteristics of the Android embedded system, the requirements on the network request library may be more stringent. HttpUrlConnection and HttpClient have been removed from the system source code since API23, and additional third-party libraries may need to be introduced if network requests are to be used during development. The commonly used network request libraries include Android-Async-HTTP, Volley, Okhttp and Retrofit. Retrofit is not really a web request library, but rather a web wrapper library that encapsulates the underlying web request library into a RESTful API design style. Android-async-http is based on HttpClient encapsulation, mainly encapsulates the switch between asynchronous thread and main thread, intelligent request retry, persistence, cookie saving to SP, etc. Vollery is one of the best known Android request libraries. Based on HttpUrlConnection, Vollery supports image loading, network request sorting, priority processing caching and Activity lifecycle interlinking. Vollery’s underlying network requests can be replaced with other network request libraries. OkHttp is the most popular network library in recent years, and now almost all app network request libraries use OkHttp or are customized based on OkHttp. OkHttp is no longer based on HttpUrlConnection, but uses sockets to implement a set of network requests. OkHttp supports synchronization and asynchronism, encapsulating thread pooling, data conversion, parameter usage, error handling, and more. And sockets also use NIO to improve the performance of network requests to a greater extent under non-blocking technology.

OkHttp demo use

package com.benson.android.network import android.graphics.Color import android.os.Bundle import android.view.Gravity import android.view.ViewGroup import android.widget.* import androidx.appcompat.app.AppCompatActivity import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import okhttp3.OkHttpClient import okhttp3.Request import java.lang.StringBuilder import java.util.concurrent.TimeUnit class Okhttp3DemoActivity: AppCompatActivity () {val client by lazy {OkHttpClient. Builder (). RetryOnConnectionFailure (true) / / connection timeout retry .connecttimeout (2L, timeunit.minutes) // 2s connection timeout. ReadTimeout (2L, timeunit.minutes) // 2s readTimeout. WriteTimeout (2L, writeTimeout) Timeunit.minutes) // 2s write timeout. Build ()} Override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val content = LinearLayout(this) content.orientation = LinearLayout.VERTICAL initView(content) setContentView(content) } private fun initView(content: ViewGroup) { val searchBar = LinearLayout(this) searchBar.orientation = LinearLayout.HORIZONTAL searchBar.gravity = Gravity.CENTER val input = EditText(this) input.textSize = DisplayUtil.sp2px(this, 10.0 F) * 1.0 F searchBar. AddView (input, LinearLayout. LayoutParams (0, LinearLayout. LayoutParams. MATCH_PARENT, 7.0f)) val searchBtn = Button(this) searchbtn. text = "search" searchbtn. textSize = displayUtil.sp2px (this, 10.0 F) * 1.0 F searchBar. AddView (searchBtn LinearLayout. LayoutParams (0, LinearLayout. LayoutParams. MATCH_PARENT, 2.0 F)) content. addView (searchBar, LinearLayout. LayoutParams (LinearLayout. LayoutParams. MATCH_PARENT, 0, 1.0 F)) val result = TextView (this) result. SetBackgroundColor (Color. BLACK) result. SetTextColor (Color. WHITE) result.textSize = DisplayUtil.sp2px(this, 15.0f) * 1.0f val scrollView = scrollView (this) scrollView.addView(result) content.addView(scrollView, LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, (10.0 F)) searchBtn. SetOnClickListener {search result, the input. The text. The toString ())}} private fun search (result: TextView, url: String) { GlobalScope.launch { val request = Request.Builder() .url(url) .get() .build() val response = client.newCall(request).execute() val content = response.body?.string() val resultStr = StringBuilder() resultStr.append("code=").append(response.code).append("\n") .append("msg=").append(response.message).append("\n") .append("headers=").append(response.headers).append("\n") .append(content) result.post { result.text = resultStr.toString() } } } }Copy the code

The effect picture is as followsIn this case, a synchronous call is used. Execute returns a Response object in which the parameters of the Http request result can be obtained.

Source code parsing – creation of OkHttpClient

OkHttp3’s request process is simple and easy to use. Let’s look at OkHttp3 in terms of both the creation and request processes. OkHttp’s Manager object is OkHttpClient, also created using the Builder pattern. The following are the properties of okHttpClient.Builder

Var dispatcher: dispatcher = dispatcher () ConnectionPool = ConnectionPool() MutableList<Interceptor> = mutableListOf(); internal val networkInterceptors are used for non-websocket requests: MutableList<Interceptor> = mutableListOf(); EventListener. Factory = EventListener. NONE. AsFactory () / / whether to support the connection fails retry internal var retryOnConnectionFailure = true / / Verify related internal Var Authenticator: Authenticator = Authenticator.NONE internal var followRedirects = true internal var followSslRedirects = true internal var cookieJar: CookieJar = CookieJar.NO_COOKIES internal var cache: Cache? = null internal var DNS: DNS = dns. SYSTEM // proxy related internal var proxy: proxy? = null internal var proxySelector: ProxySelector? = null internal var proxyAuthenticator: Authenticator = Authenticator.NONE internal var socketFactory: SocketFactory = SocketFactory.getDefault() internal var sslSocketFactoryOrNull: SSLSocketFactory? = null internal var x509TrustManagerOrNull: X509TrustManager? = null internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT internal var certificateChainCleaner: CertificateChainCleaner? = NULL internal var callTimeout = 0 // Connection timeout internal var connectTimeout = 10_000 // Data reading timeout internal var readTimeout = 10_000 // Write data times out internal var writeTimeout = 10_000 internal var pingInterval = 0 internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE internal var routeDatabase: RouteDatabase? = nullCopy the code

The meaning of each attribute is shown in the comments, and once these attributes are constructed with the Builder as an OkHttpClient, they are equivalent to a configuration that is applied every time the request is executed.

Source code parsing – Request creation

The Request is created using the Builder mode, including URL, method, headers and body parameters, which are also parameters of the HTTP Request. A URL is the address of a request and can contain parameters of a GET request. Method indicates the HTTP request type, including GET, POST, PUT, and DELETE. Header is the request header, which contains information such as cookies and validation. Body is used for the body of a POST request, such as a file upload. Request is also just a configuration class that is read when actually requested.

Source code parsing – request process

OkHttpClient first constructs a Call object using Request. The implementation class of the Call interface is RealCall. The Call object can be executed synchronously and asynchronously by calling the execute and enQueue methods, respectively. Execute in RealCall is implemented as follows

override fun execute(): Response {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    timeout.enter()
    callStart()
    try {
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher.finished(this)
    }
  }
Copy the code

First check whether you executed the Call object or true. Otherwise, async is executed. Then the timeout timer is called and then the callStart is called to notify the event listener that the callStart has been called. Execute focuses on the last three calls to the dispatcher method of OkHttpClient to add the Call object to the runningSyncCalls queue, GetResponseWithInterceptorChain method to obtain the request object as a result, in the finally call OkHttpClient dispatcher finished method, will call object removed from the call queue, Execute idleCallback’s run method at the same time. In the three method calls getResponseWithInterceptorChain method is the one of the most important. Look at the RealCall getResponseWithInterceptorChain implementation

@Throws(IOException::class) internal fun getResponseWithInterceptorChain(): Response { // Build a full stack of interceptors. val interceptors = mutableListOf<Interceptor>() interceptors += client.interceptors interceptors += RetryAndFollowUpInterceptor(client) interceptors += BridgeInterceptor(client.cookieJar) interceptors += CacheInterceptor(client.cache) interceptors += ConnectInterceptor if (! forWebSocket) { interceptors += client.networkInterceptors } interceptors += CallServerInterceptor(forWebSocket) val chain = RealInterceptorChain( call = this, interceptors = interceptors, index = 0, exchange = null, request = originalRequest, connectTimeoutMillis = client.connectTimeoutMillis, readTimeoutMillis = client.readTimeoutMillis, writeTimeoutMillis = client.writeTimeoutMillis ) var calledNoMoreExchanges = false try { val response = chain.proceed(originalRequest) if (isCanceled()) { response.closeQuietly() throw IOException("Canceled") } return response } catch (e: IOException) { calledNoMoreExchanges = true throw noMoreExchanges(e) as Throwable } finally { if (! calledNoMoreExchanges) { noMoreExchanges(null) } } }Copy the code

We first create a list of interceptors and add them to the interceptors passed in when OkHttpClient was created. Also add the request to redirect the interceptor RetryAndFollowUpInterceptor, cookie blocker BridgeInterceptor, cache interceptor blocker ConnectInterceptor CacheInterceptor, and network connections, If the request is not a WebSocket request, networkInterceptors from OkHttpClient are added to the list of interceptors. The last one is the CallServerInterceptor, which actually performs the network request and resolves the result of the request. When the list of interceptors is constructed, new generates a chain of interceptors, RealInterceptorChain, and calls the Proceed method of the RealInterceptorChain to start executing the interceptor-by-interceptor intercept method. The Proceed method of the RealInterceptorChain calls copy each time it creates a new RealInterceptorChain object, which is then used to execute the next Interceptor. The purpose is for each Interceptor to have a new execution environment. The chain contains the execution environment of the Interceptor, so that each Interceptor execution does not affect each other. Generally speaking, the chain interceptor execution is a chain of responsibility mode, each interceptor can realize some functions in the network request process, and finally the CallInterceptor completes the final network request. Each intermediate interceptor ends up calling the chain’s proceed method in the intercepting method to return the result, which acts as a recursive call that lets the chain of responsibilities execute section by section. The OkHttp request execution process is actually embedded in the provided default interceptors.

The default interceptor for OkHttp

First is the request to redirect interceptor RetryAndFollowUpInterceptor, this interceptor will process the request error retry the operation. Inside the intercept method is a while(true) loop that calls the chain’s proceed method in the body of the loop, which continually executes the list of interceptors that follow. Proceed the catch up, and call the RetryAndFollowUpInterceptor recover method in the catch, the recover is used to determine whether can retry. If retries can be performed, followUpRequest is invoked to determine whether redirection is possible. FloowUpRequest takes out code in response. If code is HPPT_PROXY_AUTH(407), an agent is used. If the proxy is not an HTTP request, throw an exception, otherwise use code authentication and return a Request object. If the code is HTTP_UNAUTHORIZED(401), user authentication is required. If code is HTTP_PERM_REDIRECT(308), HTTP_TEMP_REDIRECT(307), HTTP_MULT_CHOICE(300), HTTP_MOVED_PERM(301), HTTP_MOVED_TEMP(302) and HTTP_SEE_OTHER(303) represent redirects. BuildRedirectRequest is called to construct a new redirected Request object. If code is HTTP_CLIENT_TIMEOUT(408) and there is no hit, a request that returns a response is executed. After followUpRequest gets a new request object, it continues the request in the while loop and executes the subsequent interceptors again. The while loop does not see the break keyword, so the only way out of the while loop is to throw an exception or return. A return returns a response to the start execute. Exceptions can be thrown. For example, the request does not support retries and is abnormal, or the number of retries reaches the upper limit. Then there is the BridgeInterceptor, which converts OkHttp fields into Http fields and processes cookies. In this case, it adds some default Http request parameters to the request. If these parameters are not passed in when the request is constructed, The BridgeInterceptor default parameter is used to construct a complete Http request header and body. In the middle of the BridgeInterceptor intercept, the chain. Proceed call continues the subsequent interceptor and gets a Response, which is further parsed by the BridgeInterceptor. So the real purpose of this BridgeInterceptor is to add some default parameters to the request before the actual request, and convert the parameters passed into HTTP parameters. For example, the content_type is converted into “content-type” in the header. The result of the request is further parsed. The third CacheInterceptor is a CacheInterceptor. In its Intercept method, it first fetches a cacheCandidate object from a cache object, which is a cache object. The default is the URL as the cache key identification. Then use cacheStrategy.factory (now, chain-.request (), cacheCandidate).compute() to fetch the cached request and response. Here, if request is null and response is also null, then it should be taken from the cache, but the cache is gone, so an empty response is given. If request is null but response is not, it indicates that the cache is successfully fetched and the final response is constructed with the cached response. Proceed. If the code in response is HTTP_NOT_MODIFIED(304), the server tells the client that the cache is still available. Then, response is still constructed by using the cached response, otherwise, the response returned by network request is used, and the request and response are stored in the cache. The ConnectInterceptor only opens a network connection that can be used by either the response that has been returned or the cache response that has failed. The OkHttp framework’s network connection is represented by an Exchange object. Finally, there is the CallServierInterceptor, which will eventually be used to make real network requests.

override fun intercept(chain: Interceptor.Chain): Response { val realChain = chain as RealInterceptorChain val exchange = realChain.exchange!! val request = realChain.request val requestBody = request.body val sentRequestMillis = System.currentTimeMillis() exchange.writeRequestHeaders(request) var invokeStartEvent = true var responseBuilder: Response.Builder? = null if (HttpMethod.permitsRequestBody(request.method) && requestBody ! = 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".equals(request.header("Expect"), ignoreCase = true)) { exchange.flushRequest() responseBuilder = exchange.readResponseHeaders(expectContinue = true) exchange.responseHeadersStart() invokeStartEvent = false } if (responseBuilder == null) { if (requestBody.isDuplex()) { // Prepare a duplex body so that the application can send a request body later. exchange.flushRequest() val bufferedRequestBody = exchange.createRequestBody(request, true).buffer() requestBody.writeTo(bufferedRequestBody) } else { // Write the request body if the "Expect: 100-continue" expectation was met. val bufferedRequestBody = exchange.createRequestBody(request, false).buffer() requestBody.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() } if (requestBody == null || ! requestBody.isDuplex()) { exchange.finishRequest() } if (responseBuilder == null) { responseBuilder = exchange.readResponseHeaders(expectContinue = false)!! if (invokeStartEvent) { exchange.responseHeadersStart() invokeStartEvent = false } } var response = responseBuilder .request(request) .handshake(exchange.connection.handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build() var 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 status. responseBuilder  = exchange.readResponseHeaders(expectContinue = false)!! if (invokeStartEvent) { exchange.responseHeadersStart() } response = responseBuilder .request(request) .handshake(exchange.connection.handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build() code = response.code } exchange.responseHeadersEnd(response) response = if (forWebSocket && code == 101) { // Connection is upgrading, but we need to ensure interceptors see a non-null response body. response.newBuilder() .body(EMPTY_RESPONSE) .build() } else { response.newBuilder() .body(exchange.openResponseBody(response)) .build() } if ("close".equals(response.request.header("Connection"), ignoreCase = true) || "close".equals(response.header("Connection"), ignoreCase = true)) { exchange.noNewExchangesOnConnection() } if ((code == 204 || code == 205) && response.body? .contentLength() ? : -1L > 0L) { throw ProtocolException( "HTTP $code had non-zero Content-Length: ${response.body? .contentLength()}") } return response }Copy the code

First calls the exchange. WriteRequestHeaders request written to a connection, if the request is not a GET or PUT, must first to handle the request, using exchange create Sink object and requestBody written into the Sink. We need to go back to the ConnectInterceptor to see what the underlying implementation of a connection is if one is created. As mentioned earlier, the Exchange in the Interceptor chain is an abstraction for a connection, and it is initialized in a ConnectInterceptor. RealCall’s initExchange method is called, in which exchangeFinder is used, which has a connection pool and an address built from the requested url, and the connection pool is the OkHttpClient connection pool, This means that all requests from the same OkHttpClient share the same connection pool, and when the same OkHttpClient sends multiple requests to the same address, it may be possible to reuse the connection. Calling its find method with exchangeFinder creates an exchange dec object, and calling the findHealthyConnection method in the find method retrits a connection object, which, as you can see from the method name, goes to the connection pool to find reusable connections. The findHealthConnection method is called in a while loop to get a connection, and return if the connection is judged to be isHealth. Proceed to the findConnection method, first determine if the call object already has a connection, and if so, reuse the connection. Otherwise again call RealConnectionPool callAcquirePooledConnection methods obtained from the connection pool, connection pool has a RealConnection ConcurrentLinkedQueue used to store the connection, Loop to determine that the connection in the pool can be used by the incoming address, set the connection to the Call object and return the connection object. If no connection is available from the connection pool, a new RealConnection object is created and its connect method is called to create a new connection. Connect calls the connectSocket method to create a Socket object, and stores the Socket in the rawSocket property of RealConnection. After creating the Socket, call its Connect method to establish a network connection with the server. In the CallServerInterceptor, when the request body is finished, the flushRequest method is called in the flushRequest method, which calls the flush method of BufferSink. This method eventually calls write of the Socket’s OutputStream, writing data to the server connection. Finished writing the request header data need to start reading the server returns the HTTP result, called first exchange. ReadResponseHeaders read the response headers, while reading the response, the response of the code, the message and protocol at the same time to resolve. If the response body is null, the request body data is written to the server. Construct the responseBuilder read into a response object, judge if it is not websocket and code is not 101, then request response body data, so that a request response is fully constructed, and finally return the response to the interception chain.

conclusion

Based on the responsibility chain pattern, the OkHttp framework breaks a request into several small processes, each of which is handled by one Interceptor, and users can extend the request process by inserting custom interceptors in front of all the built-in interceptors. The network connection part of the OkHttp framework is abstracted using Exchange objects. The Interceptor only implements the request logic, and the specific data reads and writes are done by Exchange. Therefore, the underlying data requests can be replaced theoretically. The whole framework has low coupling between logic processing and data processing and strong expansibility.