The code is not difficult to write 2022-01-26 21:51:19 阅读数:338
OkHttp
Can be said to be Android
The most common network request framework in development ,OkHttp
Easy to use , Extensibility is strong , Powerful ,OKHttp
Source code and principle are also frequent visitors in the interview
however OKHttp
The content of the source code is more , Want to learn its source code is often a myriad of things , I can't grasp the key point at the moment .
This paper starts from several problems OKHttp
Related knowledge , In order to quickly build OKHttp
Knowledge system , If it works for you , Welcome to thumb up ~
This article mainly includes the following contents
OKHttp
What is the overall process of the request ?OKHttp
How the dispenser works ?OKHttp
How the interceptor works ?OKHttp
How to reuse TCP
Connect ?OKHttp
How to clear idle connections ?OKHttp
What are the advantages ?OKHttp
What design patterns are used in the framework ?OKHttp
Introduction to the overall request process First, let's look at the simplest Http
How is the request sent .
val okHttpClient = OkHttpClient()
val request: Request = Request.Builder()
.url("https://www.google.com/")
.build()
okHttpClient.newCall(request).enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
}
})
This code looks simple ,OkHttp
At least only contact is required during the request process OkHttpClient
、Request
、Call
、 Response
, However, there will be a lot of logical processing inside the framework .
Most of the logic of all network requests is concentrated in interceptors , But before entering the interceptor, you need to rely on the distributor to allocate the request task .
About distributors and interceptors , Let's briefly introduce , There will be more detailed explanations later
The whole network request process is roughly as shown above
OKHttpClient
And Request
OKHttpClient
adopt newCall
Initiate a new request OKHttp
How the dispenser works ?The main function of the distributor is to maintain the request queue and thread pool , For example, we have 100 Asynchronous requests , They must not be requested at the same time , Instead, they should be queued into categories , It is divided into the list in request and the list waiting , When the request is completed , You can take the waiting request from the waiting list , To complete all requests
Here, synchronous requests are slightly different from asynchronous requests
Synchronization request
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
Because synchronous requests do not require a thread pool , There are no restrictions . So the distributor just makes a record . The subsequent requests can be synchronized in the order of joining the queue
Asynchronous requests
synchronized void enqueue(AsyncCall call) {
// The maximum number of requests cannot exceed 64, same Host Request cannot exceed 5 individual
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
When the task being executed does not exceed the maximum limit 64, Same at the same time Host
Your request does not exceed 5 individual , Will be added to the executing queue , Submit to the thread pool at the same time . Otherwise, join the waiting queue first .
After each mission , Will call the distributor finished
Method , This will take out the tasks in the waiting queue and continue to execute
OKHttp
How the interceptor works ?Task distribution through the distributor above , Next, we will start a series of configurations with the interceptor
# RealCall
override fun execute(): Response {
try {
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
Let's see RealCall
Of execute
Method , It can be seen that , Finally, I return to getResponseWithInterceptorChain
, The construction and handling of responsibility chain is actually in this method
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
)
val response = chain.proceed(originalRequest)
}
As shown above , Built a OkHttp
The interceptor's chain of responsibility
Responsibility chain , seeing the name of a thing one thinks of its function , It is an execution chain used to deal with related affairs , There are multiple nodes on the execution chain , Every node has a chance ( Matching conditions ) Processing request transactions , If a node has finished processing, it can be transferred to the next node to continue processing or return to the end of processing according to the actual business needs .
As shown above, the order and function of adding responsibility chain are shown in the following table :
Interceptor | effect |
---|---|
Apply interceptors | Got the original request , You can add some custom header、 General parameters 、 Parameter encryption 、 Gateway access, etc . |
RetryAndFollowUpInterceptor | Handle error retries and redirects |
BridgeInterceptor | Bridging interceptors at application layer and network layer , The main work is to add cookie、 Add fixed header, such as Host、Content-Length、Content-Type、User-Agent wait , Then save the results of the response cookie, If the response uses gzip Compression , You also need to decompress . |
CacheInterceptor | Cache interceptor , If the cache is hit, the network request will not be initiated . |
ConnectInterceptor | Connection interceptor , A connection pool is maintained internally , Responsible for connection reuse 、 Create connection ( Three handshakes, etc )、 Release the connection and create the connection socket flow . |
networkInterceptors( Network interceptor ) | User defined interceptors , It is usually used to monitor the data transmission at the network layer . |
CallServerInterceptor | Request interceptor , After pre preparation , Actually initiated a network request . |
In this way, our network requests pass through the responsibility chain and pass down level by level , Eventually it will be executed CallServerInterceptor
Of intercept
Method , This method encapsulates the result of the network response into a Response
Object and return
. Then go back level by level along the responsibility chain , Finally back to getResponseWithInterceptorChain
Method return , As shown in the figure below :
From the whole responsibility link , The application interceptor is the first interceptor to execute , That is, the user sets it himself request
Original request after property , The network interceptor is located in ConnectInterceptor
and CallServerInterceptor
Between , At this point, the network link is ready , Just wait to send request data . They mainly have the following differences
RetryAndFollowUpInterceptor
and CacheInterceptor
Before , So in case of an error, try again or network redirection , The network interceptor may execute multiple times , Because it's equivalent to making a second request , But the application interceptor will always trigger only once . And if it's in CacheInterceptor
If you hit the cache, you don't need to go through the network request , Therefore, there will be a short circuit network interceptor .CallServerInterceptor
outside , Each interceptor should call... At least once realChain.proceed
Method . In fact, in the application interceptor layer, you can call proceed
Method ( Local exception retry ) Or not proceed
Method ( interrupt ), But the network interceptor layer connection is ready , Can and can only be called once proceed
Method .OKHttp
How to reuse TCP
Connect ?ConnectInterceptor
My main job is to establish TCP
Connect , establish TCP
The connection requires three handshakes and four waves , If each HTTP
All requests need to create a new TCP
It consumes more resources
and Http1.1
Has supported keep-alive
, That is many Http
Request to reuse one TCP
Connect ,OKHttp
We have also made corresponding optimization , So let's see OKHttp
How to reuse TCP
Connected
ConnectInterceptor
The code to find the connection in will eventually call ExchangeFinder.findConnection
Method , As follows :
# ExchangeFinder
// To host a new data stream seek Connect . The search order is Assigned connections 、 Connection pool 、 make new connection
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
synchronized (connectionPool) {
// 1. Try to use Connection assigned to data flow .( For example, when redirecting a request , The last requested connection can be reused )
releasedConnection = transmitter.connection;
result = transmitter.connection;
if (result == null) {
// 2. There are no assigned connections available , Just try to get... From the connection pool .( Connection pooling will be explained in detail later )
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
result = transmitter.connection;
}
}
}
synchronized (connectionPool) {
if (newRouteSelection) {
//3. There is now a IP Address , Try again to get... From the connection pool . May match due to connection merging .( Here comes routes, From above null)
routes = routeSelection.getAll();
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, false)) {
foundPooledConnection = true;
result = transmitter.connection;
}
}
// 4. The second time didn't succeed , Just put the new connection , Conduct TCP + TLS handshake , Establish a connection with the server . Blocking operation
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
synchronized (connectionPool) {
// 5. The last attempt to get from the connection pool , Note that the last parameter is true, I.e. requirement Multiplexing (http2.0)
// intend , If this time is http2.0, So to make sure Multiplexing ,( Because the above handshake operation is not thread safe ) It will confirm again whether the same connection exists in the connection pool at this time
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
// If you get , Just close the connection we created , Return the obtained connection
result = transmitter.connection;
} else {
// If there's no last attempt , Just save the newly created connection into the connection pool
connectionPool.put(result);
}
}
return result;
}
Some of the code has been simplified above , It can be seen that , The connection interceptor uses 5 There are two ways to find connections
address
Agreement ——host
、port
、 Agent, etc , And the matching connection can accept new requests .routes
Try again to get , This is mainly aimed at Http2.0
An operation of ,Http2.0
You can reuse square.com
And square.ca
The connection of RealConnection
example , Conduct TCP + TLS
handshake , Establish a connection with the server .Http2.0
Multiplexing of connections , Will match from the connection pool for the third time . Because the handshake process of the newly established connection is non thread safe , Therefore, at this time, the same connection may be newly stored in the connection pool .The above is the operation of the connection interceptor trying to reuse the connection , The flow chart is as follows :
OKHttp
How to clear idle connections ? It says we'll build one TCP
Connection pool , But if there's no mission , Idle connections should also be cleared in time ,OKHttp
how ?
# RealConnectionPool
private val cleanupQueue: TaskQueue = taskRunner.newQueue()
private val cleanupTask = object : Task("$okHttpName ConnectionPool") {
override fun runOnce(): Long = cleanup(System.nanoTime())
}
long cleanup(long now) {
int inUseConnectionCount = 0;// Number of connections in use
int idleConnectionCount = 0;// Number of idle connections
RealConnection longestIdleConnection = null;// The connection with the longest idle time
long longestIdleDurationNs = Long.MIN_VALUE;// The longest free time
// Traversal connection : Find the connection to be cleaned , Find the time for the next cleanup ( It's not the maximum free time yet )
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
// If the connection is in use ,continue, Number of connections in use +1
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
// Number of idle connections +1
idleConnectionCount++;
// Assign the longest idle time and the corresponding connection
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
// If the longest idle time is greater than 5 minute or Free number Greater than 5, Just remove and close the connection
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// else, Just go back to How long will it take to arrive 5 minute , then wait This time to clean up
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// There are no idle connections , Just 5 Try cleaning again in minutes .
return keepAliveDurationNs;
} else {
// No connection , Don't clean up
cleanupRunning = false;
return -1;
}
}
// Close the removed connection
closeQuietly(longestIdleConnection.socket());
// After closing and removing immediately Carry on the next Try to clean up
return 0;
}
The idea is still very clear :
The flow is shown in the following figure :
OKHttp
What are the advantages ?OkHttpClient
Unity is exposed .Spdy
、Http1.X
、Http2
、 as well as WebSocket
Other protocols TCP
(Socket
), Reduce request latency GZIP
Reduce data traffic ip
, Automatic redirection OKHttp
What design patterns are used in the framework ?OkHttpClient
And Request
The builder pattern is used in the construction of OkHttp
Using the appearance mode , Hide the complexity of the whole system , Interface the subsystem through a client OkHttpClient
Unity is exposed .OKHttp
The core of is the responsibility chain model , adopt 5 The responsibility chain composed of two default interceptors completes the configuration of the request OKHttp
Reuse TCP
Connection pool is used in connection , At the same time, thread pool is also used in asynchronous requests This article mainly combs OKHttp
Principle related knowledge points , And answered the following questions :
OKHttp
What is the overall process of the request ?OKHttp
How the dispenser works ?OKHttp
How the interceptor works ?OKHttp
How to reuse TCP
Connect ?OKHttp
How to clear idle connections ?OKHttp
What are the advantages ?OKHttp
What design patterns are used in the framework ?If it helps you , Welcome to thumb up , thank you ~
In this paper, from https://juejin.cn/post/7020027832977850381, If there is any infringement , Please contact to delete .
copyright:author[The code is not difficult to write],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/01/202201262151176452.html