In the field of Android development, many important issues have a good open source solution, such as network request OkHttp + Retrofit is simply the choice. "We don't have to reinvent the wheel to say that we don't need to know how to build a wheel and how to make it better!" "After nearly two years of using these good wheels, it's time to take the wheel apart. Based on the latest source code of OkHttp up to 2016.7.11, this paper analyzes it in detail.
1, the whole idea
Starting from the use of the method, the first is how to use, followed by the function we use in the internal how to achieve, the implementation of the scheme has what skills, what paradigm. The full text is basically a OkHttp source of an analysis and guidance, very recommended that you download OkHttp source code, followed by this article, over the source code. For the skills and paradigm, because at present my skill is not in place, the analysis content is not much, welcome everybody and I discuss together.
First put a complete flow chart (do not understand the matter, slowly looking back):
2, basic use Cases
From the official website of OkHttp.
2.1, create the Okhttpclient object [code]java code:
Okhttpclient client = new Okhttpclient ();
Hey, how come you don't see builder? Don't worry, and look at the constructor:
[Code]java Code:
Public Okhttpclient () {This (new Builder ());}
The original is convenient for us to use, providing a "quick action", all using the default configuration. There OkHttpClient.Builder
are a lot of class members, and we'll analyze it later, here we go briefly:
[Code]java Code:
Public Builder () { dispatcher = new Dispatcher (); protocols = Default_protocols; Connectionspecs = Default_connection_specs; Proxyselector = Proxyselector.getdefault (); 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; ConnectTimeout = 10_000; ReadTimeout = 10_000; WriteTimeout = 10_000;}
2.2, initiate the HTTP request [Code]java code:
String run (string url) throws IOException { Request request = new Request.builder () . URL (URL) . Build (); Response Response = client.newcall (Request). Execute (); Return Response.body (). String ();}
OkHttpClient
Implemented Call.Factory
, responsible for creating new on request Call
, in Demolition Wheel series: Demolition Retrofit We had a brief encounter with it:
callFactory
Responsible for creating the HTTP request, the HTTP request is abstracted to the okhttp3.Call
class, which represents an HTTP request that is ready to be executed at any time
So let's see how it's created call now:
[Code]java Code:
/** * Prepares the {@code request} to is executed at some point in the future. */@Override Public call Newcall (Request request) { return new Realcall (this, request);}
So it seems that the credit is all in RealCall
class, below we analyze the process of synchronizing the network request, while understanding RealCall
the specific content.
2.2.1, synchronizing network requests
First we look at RealCall#execute
:
[Code]java Code:
@Override public Response Execute () throws IOException { synchronized (this) { if (executed) throw new Illegalstat Eexception ("Already Executed"); (1) executed = true; } try { client.dispatcher (). executed (this); (2) Response result = Getresponsewithinterceptorchain (); (3) if (result = = null) throw new IOException ("Canceled"); return result; } finally { client.dispatcher (). finished (this); (4) }}
Here are 4 things we do:
- Check that the call has been executed, that each call can only be executed once, and if you want an exact call, you can clone it using the
call#clone
method.
- Using it for
client.dispatcher().executed(this)
actual execution, dispatcher
is one of the OkHttpClient.Builder
members just seen, and its documentation says that it is an execution strategy for an asynchronous HTTP request, and now it seems that the synchronization request is also mixed.
- The call
getResponseWithInterceptorChain()
function gets the HTTP return result, as you can see from the function name, this step also carries out a series of "intercept" operations.
- Finally, you have to inform yourself that the
dispatcher
execution is complete.
Dispatcher here we do not over-focus, in the process of synchronous execution, the content involved in dispatcher is simply to inform it of our execution state, such as the start of execution (call executed
), such as execution completed (call finished
), It will have more involvement in the asynchronous execution process.
To actually make a network request, parse the return result, or getResponseWithInterceptorChain
:
[Code]java Code:
Private Response Getresponsewithinterceptorchain () throws IOException { //Build a full stack of interceptors. List interceptors = new arraylist<> (); Interceptors.addall (Client.interceptors ()); Interceptors.add (retryandfollowupinterceptor); Interceptors.add (New Bridgeinterceptor (Client.cookiejar ())); Interceptors.add (New Cacheinterceptor (Client.internalcache ())); Interceptors.add (new Connectinterceptor (client)); if (!retryandfollowupinterceptor.isforwebsocket ()) { Interceptors.addall (client.networkinterceptors ()); } Interceptors.add (New Callserverinterceptor ( Retryandfollowupinterceptor.isforwebsocket ())); Interceptor.chain Chain = new Realinterceptorchain ( interceptors, NULL, NULL, NULL, 0, originalrequest); Return Chain.proceed (originalrequest);}
In one of the OkHttp developers who introduced OkHttp's article, the author said:
The whole thing is just a stack of built-in interceptors.
Visible Interceptor
is OkHttp the core of a thing, do not mistakenly think it is only responsible for intercepting requests for some additional processing (such as cookies), in fact, it is the actual network requests, caching, transparent compression and other functions are unified, each function is only one Interceptor
, they are connected to a Interceptor.Chain
, interlocking, and finally successfully completed a network request.
From getResponseWithInterceptorChain
the function we can see that Interceptor.Chain
the distribution is sequentially:
- Set at the
OkHttpClient
time of configuration interceptors
;
- Responsible for failure retry and redirection
RetryAndFollowUpInterceptor
;
- Responsible for translating user-constructed requests into requests sent to the server and translating the responses returned by the server into user-friendly responses
BridgeInterceptor
;
- Responsible for reading the cache directly back, update the cache
CacheInterceptor
;
- Responsible for establishing the connection with the server
ConnectInterceptor
;
OkHttpClient
set when configured networkInterceptors
;
- Responsible for sending the request data to the server, reading the response data from the server
CallServerInterceptor
.
Here, the position determines the function, the last interceptor must be responsible for the actual communication with the server, redirection, caching, etc. must be before the actual communication.
The chain of responsibility has been Interceptor
well practiced in this chain (thanks to stay gave away, inferiority).
It contains command objects and a series of processing objects, each processing object determines which command objects it can handle, and it knows how to pass a Command object that it cannot handle to the next processing object in the chain. The pattern also describes how to add a new processing object to the end of the processing chain.
For this to happen, Request
Response
each of Interceptor
us is likely to accomplish this, so we follow the chain to make each Interceptor
decision whether to complete the task and how to accomplish the task (self-reliance or the next one Interceptor
). In this way, the completion of the network request is completely stripped out of the RealCall
class, simplifying the respective responsibilities and logic. Two words: Elegant!
The responsibility chain model also has the typical practice in the Android system, for example the view system to the Click event (touchevent) processing, specifically may refer to the Android design pattern source code Analysis responsibility chain pattern related analysis.
Back to OkHttp, here we first briefly analyze ConnectInterceptor
and CallServerInterceptor
, see how OkHttp is the actual communication with the server.
2.2.1.1, establish the connection:
ConnectInterceptor
[Code]java Code:
@Override public Response Intercept (Chain Chain) throws IOException { Realinterceptorchain Realchain = ( Realinterceptorchain) chain; Request Request = Realchain.request (); Streamallocation streamallocation = Realchain.streamallocation (); We need the network to satisfy this request. Possibly for validating a conditional GET. Boolean doextensivehealthchecks =!request.method (). Equals ("GET"); Httpcodec Httpcodec = streamallocation.newstream (client, doextensivehealthchecks); Realconnection connection = Streamallocation.connection (); return Realchain.proceed (Request, Streamallocation, httpcodec, connection);}
Actually establishing a connection is creating an HttpCodec
object that will be used in a later step, and where is it sacred? It is an abstraction of the HTTP protocol operation, with two implementations: Http1Codec
and Http2Codec
, as the name implies, they correspond to the implementations of the http/1.1 and HTTP/2 versions, respectively.
In Http1Codec
, it encapsulates the read and Socket
write operations of Okio, and Okio has the opportunity to analyze them later, and now let's keep a simple understanding of them: it's the right java.io
and the java.nio
encapsulation that makes it easier and more efficient for IO operations.
While the HttpCodec
process of creating objects involves StreamAllocation
, RealConnection
and the code is longer, it is not expanded here, and this process, in a nutshell, is to find an available RealConnection
, reusable RealConnection
input and output ( BufferedSource
and) to BufferedSink
create HttpCodec
objects for subsequent steps to use.
2.2.1.2, sending and receiving data:
CallServerInterceptor
[Code]java Code:
@Override public Response Intercept (Chain Chain) throws IOException {Httpcodec Httpcodec = ((Realinterceptorchain) Chain ). Httpstream (); Streamallocation streamallocation = ((Realinterceptorchain) chain). Streamallocation (); Request Request = Chain.request (); Long Sentrequestmillis = System.currenttimemillis (); Httpcodec.writerequestheaders (Request); if (Httpmethod.permitsrequestbody (Request.method ()) && request.body () = null) {Sink requestbodyout = Httpcode C.createrequestbody (Request, Request.body (). ContentLength ()); Bufferedsink bufferedrequestbody = Okio.buffer (requestbodyout); Request.body (). WriteTo (Bufferedrequestbody); Bufferedrequestbody.close (); } httpcodec.finishrequest (); Response Response = Httpcodec.readresponseheaders (). Request (Request). Handshake (Streamallocation.connection (). h Andshake ()). Sentrequestatmillis (Sentrequestmillis). Receivedresponseatmillis (System.currenttimemillis ()). Build (); if (!forwebsocket | | respOnse.code ()! = 101) {response = Response.newbuilder (). Body (Httpcodec.openresponsebody (response)). Build (); if ("Close". Equalsignorecase (Response.request (). Header ("Connection")) | | "Close". Equalsignorecase (Response.header ("Connection"))) {streamallocation.nonewstreams (); }//Omit partial check code return response;}
We grab the trunk section:
- Send the request header to the server;
- If a request body is available, it is sent to the server;
- Read response header, first constructs an
Response
object;
- If there is response body, on the basis of 3, add body to construct a new
Response
object;
Here we can see that the core work is HttpCodec
done by the object, but HttpCodec
in fact the use of Okio, and Okio is actually used Socket
, so there is no mystery, but a layer of layer, a bit more layers.
Interceptor
in fact, the design is also a layered thinking, each Interceptor
is a layer. Why do we have to cover so many layers? Layered thinking in the TCP/IP protocol is the embodiment of the most vividly, layered simplification of each layer of logic, each layer only need to pay attention to their own responsibility (the idea of a single principle is also reflected here), and each layer through the agreed interface/protocol for cooperation (interface-oriented programming ideas), the joint completion of complex tasks.
Simplicity should be one of our ultimate pursuits, although sometimes it has to be complicated to achieve goals, but if there is another simpler way, I think no one should be reluctant to replace them.
2.2.2, initiating an asynchronous network request [code]java code:
client.newcall (Request) Enqueue (new Callback () {@Override public void OnF Ailure (call call, IOException e) {} @Override public void Onresponse (call call, Response Response) throws Ioexce ption {System.out.println (Response.body (). String ()); }});//Realcall#enqueue@override public void Enqueue (Callback responsecallback) {synchronized (this) {if (executed) throw new IllegalStateException ("Already Executed"); executed = true; } client.dispatcher (). Enqueue (New Asynccall (Responsecallback));} dispatcher#enqueuesynchronized void Enqueue (Asynccall call) {if (Runningasynccalls.size () < maxrequests && ; Runningcallsforhost (call) < Maxrequestsperhost) {Runningasynccalls.add (call); Executorservice (). Execute (call); } else {readyasynccalls.add (call); }}
Here we can see the role of dispatcher in the asynchronous execution, if the current can also execute a concurrent request, then execute immediately, otherwise join the readyAsyncCalls
queue, and the execution of the request after execution, will call the promoteCalls()
function, to the readyAsyncCalls
queue AsyncCall
"promotion "As runningAsyncCalls
, and begin to execute.
Here AsyncCall
is RealCall
an inner class, which is implemented so that it Runnable
can be committed to ExecutorService
execution, and it executes when it invokes the getResponseWithInterceptorChain()
function and passes the result to the responseCallback
upper user.
In this sense, the principle of synchronous requests and asynchronous requests is the same as the getResponseWithInterceptorChain()
Interceptor
network request logic that is implemented through the chain in the function, and asynchronous is ExecutorService
achieved by the implementation.
2.3, returns the data obtained
After the above synchronization ( Call#execute()
after execution) or asynchronous ( Callback#onResponse()
callback) request is complete, we can get the Response
response data from the object, including the HTTP status Code,status message,response header, Response body and so on. The body part is the most special, because the data returned by the server can be very large, so it has to be accessed by means of a data stream (which, of course, provides a string()
bytes()
way to read the data within the stream once), while the rest of the response is freely available.
The response body is encapsulated into the ResponseBody
class, which has two main points to note:
- Each body can only be consumed once, multiple consumption will throw an exception;
- The body must be closed, otherwise a resource leak will occur;
In 2.2.1.2, send and receive data: In the Callserverinterceptor section, we have seen the body-related code:
[Code]java Code:
if (!forwebsocket | | Response.code ()! = 101) { response = Response.newbuilder () . Body ( Httpcodec.openresponsebody (response)) . Build ();}
The HttpCodec#openResponseBody
response body, which provides a specific version of the HTTP protocol, HttpCodec
is implemented using Okio for specific data IO operations.
Here is a point worth mentioning, OkHttp on the response is very strict, HTTP status line can not have any messy data, otherwise it will throw an exception, in our company project practice, due to server problems, occasionally status line will have additional data, And the problem of the service side has no clue, cause we have to reluctantly continue to use HttpURLConnection, and the latter in some systems have a variety of other problems, such as Meizu system sent multi-part form when there is no response to the problem.
2.4,http Cache
In 2.2.1, the Synchronization network Request section, we have seen Interceptor
the layout, before establishing the connection, and the server communication, is CacheInterceptor
, before establishing a connection, we check whether the response has been cached, the cache is available, if it is directly return the cached data, otherwise the subsequent process, And before returning, writes the network data to the cache.
This code is more, but also very intuitive, mainly related to the implementation of the HTTP protocol cache details, and the specific cache logic OkHttp built-in encapsulation of a Cache
class, it uses DiskLruCache
, with a limited amount of space on disk cache, according to the LRU algorithm for cache elimination, here is no longer expanded.
We can set the OkHttpClient
object at construction time Cache
, and in its constructor we can specify the directory and cache size:
[Code]java Code:
Public Cache (File directory, long MaxSize);
And if we are not satisfied with the OkHttp built-in Cache
classes, we can implement our own InternalCache
interfaces and OkHttpClient
set them at construction time so that we can use our custom caching strategy.
3, Summary
OkHttp There are many details that are not in this article, such as HTTP2/HTTPS support, but it is important to build a clear overview. With a clear understanding of the whole, the details will be easier to drill down on if necessary.
At the end of the article, let's review the complete flowchart:
OkHttpClient
Implementation Call.Factory
, responsible for Request
creating Call
;
RealCall
For the concrete Call
implementation, its enqueue()
asynchronous interface is implemented by Dispatcher
using the ExecutorService
implementation, and the synchronization interface is consistent with the final network request execute()
getResponseWithInterceptorChain()
;
getResponseWithInterceptorChain()
In the use of Interceptor
chain, layered to achieve cache, transparent compression, network IO and other functions;
Android okhttp usage and analysis