Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to look at the OkHttp Source Code from the Design pattern

2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

Today, I will talk to you about how to look at the OkHttp source code from the design pattern, which may not be well understood by many people. In order to make you understand better, the editor has summarized the following contents for you. I hope you can get something according to this article.

Use

To read the source code, you should first start with how to use it:

Val okHttpClient = OkHttpClient () val request: Request = Request.Builder () .url (url) .build () okHttpClient.newCall (request) .enqueue (object: Callback {override fun onFailure (call: Call, e: IOException) {Log.d (TAG, "onFailure:")} override fun onResponse (call: Call, response: Response) {Log.d (TAG) "onResponse:" + response.body?.string ()}})

From this usage point of view, I have extracted four important messages:

OkHttpClient

Request

NewCall (request)

Enqueue (Callback)

In general, we can guess first:

Configure a client instance okHttpClient and a Request request, which is then encapsulated by the newCall method of okHttpClient, and finally sent out using the enqueue method and received a Callback response.

Then go to the certification one by one and look for the design patterns.

OkHttpClient

First take a look at the client object of this okhttp, that is, okHttpClient.

OkHttpClient client = new OkHttpClient.Builder () .addInterceptor (new HttpLoggingInterceptor ()) .readTimeout (500, TimeUnit.MILLISECONDS) .build ()

Here, we instantiate a client-side client of HTTP, and then configure some of its parameters, such as interceptor, timeout.

In this way, we can complete our requirements by calling an interface or method through a unified object, and the design pattern that we do not need to care about is the appearance pattern (facade pattern).

Facade Pattern hides the complexity of the system and provides the client with an interface through which the client can access the system. This type of design pattern belongs to structural pattern, which adds an interface to the existing system to hide the complexity of the system.

The key point is that we do not need to understand the complex relationship within the system and between the subsystems, we just need to send this facade, which is OkHttpClient in this case.

It exists like a receptionist, and we tell it what we need and what we need to do. Then the receptionist goes to the internal processing, all kinds of scheduling, and finally completed.

The main solution of the appearance mode is to reduce the complexity of accessing the internal subsystem of the complex system and simplify the interface between the client and it.

This pattern is also a common design pattern in three-party libraries, giving you an object that you only need to use to complete the requirements.

Of course, another obvious design pattern here is the builder pattern, which will be discussed below.

Request

Val request: Request = Request.Builder () .url (url) .build () / Request.kt open class Builder {internal var url: HttpUrl? = null internal var method: String internal var headers: Headers.Builder internal var body: RequestBody? = null constructor () {this.method = "GET" this.headers = Headers.Builder ()} open fun build (): Request {return Request (checkNotNull (url) {"url = = null"} Method, headers.build (), body, tags.toImmutableMap ()}}

As can be seen from the generated code of Request, its inner class Builder is used, and then a complete Request class with various parameters is assembled through the Builder class.

This is the typical Builder model.

The Builder pattern separates the construction of a complex object from its representation, and yes, the same build process can create different representations.

We can build different Request requests through Builder. We only need to pass in different request address url, request method method, header information headers, and request body body. (this is the format of the request message in the network request)

This kind of design pattern which can form different representations through construction is the builder pattern, which is also used a lot, mainly to facilitate us to pass in different parameters to build objects.

Another example is the construction of okHttpClient above.

NewCall (request)

The next step is to call the newCall method of the OkHttpClient class to get an interface that can call the enqueue method.

/ / use val okHttpClient = OkHttpClient () okHttpClient.newCall (request) / / OkHttpClient.kt open class OkHttpClient internal constructor (builder: Builder): Cloneable, Call.Factory, WebSocket.Factory {override fun newCall (request: Request): Call = RealCall (this, request, forWebSocket = false)} / / Call interface interface Call: Cloneable {fun execute (): Response fun enqueue (responseCallback: Callback) fun interface Factory {fun newCall (request: Request): Call}}

The newCall method is actually a method in the Call.Factory interface.

That is, the process of creating a Call is created through the newCall method of the Call.Factory interface, and the actual implementation of this method is handed over to the interface's subclass OkHttpClient.

This defines the interface that uniformly creates the object, and then it is up to the subclass to decide that the design pattern for instantiating the object is the factory pattern.

In factory mode, we do not expose the creation logic to the client when we create the object, and we point to the newly created object by using a common interface.

Of course, okhttp's factory here is a little small, with only one production line, that is, Call interface, and only one product, RealCall.

Enqueue (Callback)

The next method, enqueue, must be the top priority of the okhttp source code. I just mentioned that the newCall method actually gets the RealCall object, so we go to the enqueue method of RealCall:

Override fun enqueue (responseCallback: Callback) {client.dispatcher.enqueue (AsyncCall (responseCallback))}

Then turn to dispatcher.

/ / Dispatcher.kt val executorService: ExecutorService get () {if (executorServiceOrNull = = null) {executorServiceOrNull = ThreadPoolExecutor (0, Int.MAX_VALUE, 60, TimeUnit.SECONDS, SynchronousQueue (), threadFactory ("$okHttpName Dispatcher") False)} return executorServiceOrNull!} internal fun enqueue (call: AsyncCall) {promoteAndExecute ()} private fun promoteAndExecute (): Boolean {/ / switch threads through thread pool for (I in 0 until executableCalls.size) {val asyncCall = executableCalls [I] asyncCall.executeOn (executorService)} return isRunning} / / RealCall.kt fun executeOn (executorService: ExecutorService) {try {executorService.execute (this) success = true}}

A new class Dispatcher is used here, and the method called is asyncCall.executeOn (executorService).

This executorService parameter should be familiar to everyone, thread pool. Finally, the executorService.execute method is called to perform the thread pool task.

In fact, the concept of thread pool also uses a design pattern, which is called sharing meta-pattern.

The shared meta-mode (Flyweight Pattern) is mainly used to reduce the number of objects created to reduce memory footprint and improve performance. This type of design pattern is a structural pattern, which provides a way to reduce the number of objects and improve the object structure required by the application.

Its core is the shared object, and all the pool objects, such as thread pool, connection pool and so on, adopt the design pattern of sharing meta-pattern. Of course, there is not only thread pooling in okhttp, but also connection pooling that provides connection reuse and manages all socket connections.

Back to Dispatcher, so what is this class for? It is used to switch threads, because the enqueue we call is an asynchronous method, so we end up using a thread pool to switch threads and execute tasks.

Continue to look at the this task in execute (this).

Execute (this)

Override fun run () {threadName ("OkHttp ${redactedUrl ()}") {try {/ / get the response message And call back to Callback val response = getResponseWithInterceptorChain () responseCallback.onResponse (this@RealCall, response)} catch (e: IOException) {if (! signalledCallback) {responseCallback.onFailure (this@RealCall) E)} catch (t: Throwable) {cancel () if (! signalledCallback) {responseCallback.onFailure (this@RealCall, canceledException)}

Yes, this is the place to request the API. Get the response message response through the getResponseWithInterceptorChain method, and then call it back through the onResponse method of Callback, or through the onFailure method if there is an exception.

Does the synchronization method not use the thread pool? Look for the execute method:

Override fun execute (): Response {/ /... Return getResponseWithInterceptorChain ()}

Sure enough, the getResponseWithInterceptorChain, the response message, is returned directly through the execute method.

At this point, the general process of okhttp is over, and this part of the process is probably:

Set request message-> configure client parameters-> determine whether to use child threads based on synchronization or asynchronism-> initiate a request and get a response message-> callback result through Callback API

The rest is in the getResponseWithInterceptorChain method, which is the core of okhttp.

GetResponseWithInterceptorChain

Internal fun getResponseWithInterceptorChain (): Response {/ / Build a full stack of interceptors. Val interceptors = mutableListOf () 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 (interceptors = interceptors / /...) Val response = chain.proceed (originalRequest)}

The code is not very complicated, just add an interceptor, and then assemble a chain class, call the proceed method, and get the response message response.

Override fun proceed (request: Request): Response {/ / find the next interceptor val next = copy (index = index + 1, request = request) val interceptor = interceptors [index] val response = interceptor.intercept (next) return response}

The following code is simplified, the main logic is to get the next interceptor (index+1), and then call the interceptor's intercept method.

Then the code in the interceptor is unified in this format:

Override fun intercept (chain: Interceptor.Chain): Response {/ / do things A response = realChain.proceed (request) / / do things B}

Combine two pieces of code to form a chain that organizes the work of all connectors. Something like this:

Interceptor 1 do things A-> interceptor 2 do things A-> interceptor 3 do things A-> interceptor 3 do things B-> interceptor 2 do things B-> interceptor 1 do things B

It should be easy to understand that each interceptor is connected through the proceed method.

The last interceptor ConnectInterceptor is to separate thing An and thing B, and its function is to communicate with the server, send data to the server, and parse the read response data.

So what do things An and B mean? In fact, it represents what happened before and after the communication.

Let's do another animation:

Isn't this kind of thinking a bit like.. Recursive? Yes, it is recursive, first progressively execute thing A, and then return to doing thing B.

In fact, this recursive loop uses the responsibility chain pattern in the design pattern.

The chain of responsibility pattern (Chain of Responsibility Pattern) creates a chain of recipient objects for the request. This pattern gives the type of request and decouples the sender and receiver of the request.

To put it simply, it is to give each object a chance to process the request, and then do its own thing until the event is processed. This design pattern is also used in the event distribution mechanism in Android.

The next step is to understand what each interceptor has done, and then you can understand the whole process of okhttp, which is the content of the next issue.

First of all, a wave of warning:

AddInterceptor (Interceptor), which is set by the developer, will perform the earliest interception processing before all interceptors are processed according to the developer's requirements, such as some common parameters, Header can be added here.

RetryAndFollowUpInterceptor, here you will do some initialization work for the connection, retry work for failed requests, and subsequent request work for redirection.

BridgeInterceptor, here you will build a request for network access, and the subsequent work will convert the response Response returned from the network request into the Response available to the user, such as adding file types, adding content-length calculation, and unpacking gzip.

CacheInterceptor, which mainly deals with cache-related processing, caches the request value according to the configuration of the OkHttpClient object and the caching policy, and if there is a configurable Cache locally, you can return the cached result without network interaction.

ConnectInterceptor, here is mainly responsible for establishing a connection, will establish a TCP connection or TLS connection, as well as the HttpCodec responsible for encoding and decoding.

NetworkInterceptors, which is also set up by the developers themselves, is essentially similar to the first interceptor, but it is of different use because of its location. The interceptor added at this location can see the data of the request and response, so you can do some network debugging.

CallServerInterceptor, here is the request and response of network data, that is, the actual network Imax O operation, reading and writing data through socket.

After reading the above, do you have any further understanding of how to look at the OkHttp source code from the design pattern? If you want to know more knowledge or related content, please follow the industry information channel, thank you for your support.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report