preface
Without going into too much detail here, OkHttp is one of the major web frameworks in Android development. It can also be found in many well-known frameworks, such as Glide, Retrofit, and so on. So, of course, we have to analyze the framework. The main difference between 4.x and 3.x is that 4.x rewrites the framework using Kotlin, while 3.x uses Java. One last word of caution, don’t forget network permissions when using network frameworks! Network access! Network access!
Network Basics
The network related knowledge is not introduced here, this part of the knowledge is very important, I hope you can master. Here is the SnailClimb JavaGuide open source project for you guys, which introduces the basic knowledge. If you are interested, you can check it out.
Method of use
private final String url_navi = "https://www.wanandroid.com/navi/json";
private final String url_friend = "https://www.wanandroid.com/friend/json";
private TextView tv_response;
Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull final Message msg) { tv_response.setText(msg.obj.toString()); }};@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_enqueue:
enqueue();
break;
case R.id.btn_execute:
execute();
break; }}private void enqueue(a) {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.get()
.url(url_friend)
.build();
final Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {}@Override
public void onResponse(Call call, Response response) throws IOException {
Message message = Message.obtain();
message.what = 1; message.obj = response.body().string(); mHandler.sendMessage(message); }}); }private void execute(a) {
final OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.get()
.url(url_navi)
.build();
new Thread(new Runnable() {
@Override
public void run(a) {
try {
Response response = okHttpClient.newCall(request).execute();
Message message = Message.obtain();
message.what = 1;
message.obj = response.body().string();
mHandler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
Copy the code
Writing this example is to use the Wandroid website open API of Hongyang Dashen, thank you very much. As we all know, you can’t do time-consuming operations in the Android main thread. So we put the network request into the child thread. When the request gets the data, the Handler passes the data to the main thread to modify the UI. Two request methods from OkHttp are used here, the enqueue method and the execute method. The former is asynchronous and the latter is synchronous, both of which are described below. Finally, let’s look at the results:
The source code parsing
When we make a network request, we first create an OkHttpClient object, so let’s take a look.
OkHttpClient
public class OkHttpClient implements Cloneable.Call.Factory.WebSocket.Factory {...public OkHttpClient(a) {
this(new Builder());
}
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
this.eventListenerFactory = builder.eventListenerFactory;
this.proxySelector = builder.proxySelector;
this.cookieJar = builder.cookieJar;
this.cache = builder.cache;
this.internalCache = builder.internalCache;
this.socketFactory = builder.socketFactory;
boolean isTLS = false;
for (ConnectionSpec spec : connectionSpecs) {
isTLS = isTLS || spec.isTls();
}
if(builder.sslSocketFactory ! =null| |! isTLS) {this.sslSocketFactory = builder.sslSocketFactory;
this.certificateChainCleaner = builder.certificateChainCleaner;
} else {
X509TrustManager trustManager = Util.platformTrustManager();
this.sslSocketFactory = newSslSocketFactory(trustManager);
this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
}
if(sslSocketFactory ! =null) {
Platform.get().configureSslSocketFactory(sslSocketFactory);
}
this.hostnameVerifier = builder.hostnameVerifier;
this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(certificateChainCleaner);
this.proxyAuthenticator = builder.proxyAuthenticator;
this.authenticator = builder.authenticator;
this.connectionPool = builder.connectionPool;
this.dns = builder.dns;
this.followSslRedirects = builder.followSslRedirects;
this.followRedirects = builder.followRedirects;
this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
this.callTimeout = builder.callTimeout;
this.connectTimeout = builder.connectTimeout;
this.readTimeout = builder.readTimeout;
this.writeTimeout = builder.writeTimeout;
this.pingInterval = builder.pingInterval;
if (interceptors.contains(null)) {
throw new IllegalStateException("Null interceptor: " + interceptors);
}
if (networkInterceptors.contains(null)) {
throw new IllegalStateException("Null network interceptor: "+ networkInterceptors); }}... }Copy the code
We call the OkHttpClient(Builder Builder) constructor inside OkHttpClient() by calling its no-argument constructor, passing in a Builder object, Assign the property fields inside OkHttpClient() via the Builder object. Let’s look at the Builder class again.
OkHttpClient->Builder
public static final class Builder {
Dispatcher dispatcher;
@Nullable Proxy proxy;
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
EventListener.Factory eventListenerFactory;
ProxySelector proxySelector;
CookieJar cookieJar;
@Nullable Cache cache;
@Nullable InternalCache internalCache;
SocketFactory socketFactory;
@Nullable SSLSocketFactory sslSocketFactory;
@Nullable CertificateChainCleaner certificateChainCleaner;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator;
Authenticator authenticator;
ConnectionPool connectionPool;
Dns dns;
boolean followSslRedirects;
boolean followRedirects;
boolean retryOnConnectionFailure;
int callTimeout;
int connectTimeout;
int readTimeout;
int writeTimeout;
int pingInterval;
public Builder(a) {
dispatcher = new Dispatcher(); // Request dispenser
protocols = DEFAULT_PROTOCOLS; // Default protocol
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(); / / the connection pool
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
callTimeout = 0;
connectTimeout = 10 _000; // Connection timeout time
readTimeout = 10 _000; // Read timeout time
writeTimeout = 10 _000; // Write timeout
pingInterval = 0; }... }Copy the code
The Builder is an inner class of OkhttpClient that assigns values to its property fields in its constructor.
Dispatcher
What is the Dispatcher class created during the Buidler assignment?
/**
* Policy on when async requests are executed.
*
* <p>Each dispatcher uses an {@link ExecutorService} to run calls internally. If you supply your
* own executor, it should be able to run {@linkplain #getMaxRequests the configured maximum} number
* of calls concurrently.
*/
public final class Dispatcher {
private int maxRequests = 64; // Maximum number of requests
private int maxRequestsPerHost = 5; // Maximum number of requests per Host
private @Nullable Runnable idleCallback;
private @Nullable ExecutorService executorService; // Thread pool object
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); // Queue of asynchronous requests to be executed
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); // Queue of asynchronous requests in progress
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); // Queue of synchronizing requests in progress
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher(a) {}// Create a thread pool. The core thread is 0. The maximum value is Integer
public synchronized ExecutorService executorService(a) {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher".false));
}
returnexecutorService; }...synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
void enqueue(AsyncCall call) {
synchronized (this) { readyAsyncCalls.add(call); } promoteAndExecute(); }... }Copy the code
As you can see from the comments on this class, this class is concerned with asynchronous requests. Also, we can see that this class sets the maximum number of requests, the maximum number of requests per Host, the thread pool, and so on. Three queues are also maintained, representing asynchronous requests ready for execution, asynchronous requests in progress, and synchronous request methods in progress. When you executed the executed method, you are actually adding a synchronous request object to the queue of ongoing synchronous requests. When the enQueue method is executed, an asynchronous request object is added to the queue of asynchronous requests ready for execution.
Request
After creating the OkhttpClient object, we create the Request object using the new Request.Builder() method and assign it to the constructor pattern.
public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
finalMap<Class<? >, Object> tags;private volatile @Nullable CacheControl cacheControl; // Lazily initialized.
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);
}
public static class Builder {
@Nullable HttpUrl url;
String method;
Headers.Builder headers;
@Nullable RequestBody body;
public Builder(a) {
this.method = "GET";
this.headers = new Headers.Builder();
}
Builder(Request request) {
this.url = request.url;
this.method = request.method;
this.body = request.body;
this.tags = request.tags.isEmpty() ? Collections.<Class<? >, Object>emptyMap() :new LinkedHashMap<>(request.tags);
this.headers = request.headers.newBuilder();
}
public Builder url(HttpUrl url) {
if (url == null) throw new NullPointerException("url == null");
this.url = url;
return this; }... }... }Copy the code
As we can see, this step is mainly to assign the Request address, Request method, Request body, and so on.
Creating a Call object
In this step, we Call the newCall method of the OkHttpClicent object created in the first step, pass in the Request object created in the second step, and return a Call object.
/** * A call is a request that has been prepared for execution. A call can be canceled. As this object * represents a single request/response pair (stream), it cannot be executed twice. */
public interface Call extends Cloneable {
// Get the original Request object that initializes the Call object
Request request(a);
// Perform a synchronization request
Response execute(a) throws IOException;
// Perform an asynchronous request
void enqueue(Callback responseCallback);
// Cancel the request
void cancel(a);
// Whether the request has been executed
boolean isExecuted(a);
// Whether the request was cancelled
boolean isCanceled(a); . }public class OkHttpClient implements Cloneable.Call.Factory.WebSocket.Factory {...@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */); }... }final class RealCall implements Call {...private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
this.timeout = new AsyncTimeout() {
@Override protected void timedOut(a) { cancel(); }};this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
}
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
returncall; }... }Copy the code
Call is an interface that defines a set of methods to operate on a request. RealCall objects implement the Call interface and override the methods in the interface. So this step actually returns a RealCall object, and from here we can see that it is the RealCall object that actually initiates the request.
RealCall -> execute
When we do the network request, we use the execute method, so let’s see.
@Override public Response execute(a) throws IOException {
synchronized (this) { // 1 Add a synchronization lock
if (executed) throw new IllegalStateException("Already Executed"); // 2 Determine whether a RealCall is being requested
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
client.dispatcher().executed(this); //3 Add the request object to the Dispatcher's executing synchronous request queue
Response result = getResponseWithInterceptorChain(); //4 Get interceptor chain
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this); //5 Remove the request object from the Dispatcher object queue.}}Copy the code
The important operations of this method are annotated, of which comment 4 is the most important and can be regarded as the best part of the OkHttp framework. This method interceptor chain, let’s take a look at this method.
RealCall -> getResponseWithInterceptorChain
Response getResponseWithInterceptorChain(a) throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors()); // Developer custom interceptor
interceptors.add(retryAndFollowUpInterceptor); // Failed to reconnect interceptor, synchronously initialized when initializing RealCall object
interceptors.add(new BridgeInterceptor(client.cookieJar())); // Bridge and adapt interceptors
interceptors.add(new CacheInterceptor(client.internalCache())); // Cache interceptor
interceptors.add(new ConnectInterceptor(client)); // Connect interceptor
if(! forWebSocket) { interceptors.addAll(client.networkInterceptors());// Network interceptor
}
interceptors.add(new CallServerInterceptor(forWebSocket)); // Request service interceptor
// Create a chain of interceptors
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null.null.null.0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
// Returns the result of interceptor chain execution
return chain.proceed(originalRequest);
}
Copy the code
We know that we can get the result of the request by executing the interceptor chain. Let’s see if the connector chain is running the process.
RealInterceptorChain
/**
* A concrete interceptor chain that carries the entire interceptor chain: all application
* interceptors, the OkHttp core, all network interceptors, and finally the network caller.
*/
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private final Call call;
private final EventListener eventListener;
private final int connectTimeout;
private final int readTimeout;
private final int writeTimeout;
private int calls;
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout; this.writeTimeout = writeTimeout; }... public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection Connection) throws IOException {Checks whether the number of interceptors exceedsif (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if(this.httpCodec ! = null && ! this.connection.supportsUrl(request.url())) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if(this.httpCodec ! = null && calls > 1) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor inRealInterceptorChain next = new RealInterceptorChain(Interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout,readTimeout, writeTimeout); // Interceptor Interceptor = interceptors.get(index); Response Response = interceptor.intercept(next); // Confirm that the next interceptor made its required call to chain.proceed().if(httpCodec ! = null && index + 1 < interceptors.size() && next.calls ! = 1) { throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response is not null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body"); } // return the request resultreturnresponse; }}Copy the code
This gives us an idea of how OkHttp works through the interceptor chain: starting with the first interceptor, the request is passed down layer by layer; After the response is received, it is passed up layer by layer and eventually back out. Does this approach sound familiar? Yes, we encountered this kind of delivery when we studied the event distribution mechanism. In fact, this kind of coding method is called the responsibility chain design pattern, and we’ll talk about it later.
RealCall -> enqueue
Now that we’ve looked at the synchronous request method execute, let’s look at the asynchronous request method enQueue.
@Override public void enqueue(Callback responseCallback) {
synchronized (this) { // add a synchronization lock
if (executed) throw new IllegalStateException("Already Executed"); // check whether the current request is being executed
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback)); //3. Execute dispatcher. enqueue.
}
public interface Callback {
// Request failed callback
void onFailure(Call call, IOException e);
// Get the response callback
void onResponse(Call call, Response response) throws IOException;
}
Copy the code
The enQueue method is called with a Callback that contains two Callback methods. 2. Add a synchronization lock to check whether the current request is executing. Create an AsyncCall and pass it in when the EnQueue method of the Dispatcher object is called.
Dispatcher -> enqueue
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call); // Add the asynchronous request object to the asynchronous request collection ready to run
}
promoteAndExecute();
}
private boolean promoteAndExecute(a) {
assert(! Thread.holdsLock(this));
Create a set of asynchronous requests that can be executed
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
// 2. Loop through the set of asynchronous requests to be run
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
Check whether the number of asynchronous requests in progress is greater than the maximum number of requests
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
// 4. Check whether the number of hosts in the current asynchronous request exceeds the upper limit
if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
// Remove the current asynchronous request
i.remove();
// add the asynchronous request to the executable set of asynchronous requests
executableCalls.add(asyncCall);
// Add an asynchronous request object to the running asynchronous request set
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
// 8. Loop through the set of executable asynchronous requests
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
Create a thread pool and execute the request
asyncCall.executeOn(executorService());
}
return isRunning;
}
Copy the code
Only two things are done in the dispatcher. enqueue method:
1. Add the asynchronous request object to the collection of asynchronous requests ready for execution. 2. Execute the promoteAndExecute method.
A few more things are done when executing the promoteAndExecute method method:
Create an executable set of asynchronous requests. 2. Loop through the set of asynchronous requests ready to run. 3. Check whether the number of asynchronous requests being executed is greater than the maximum number of requests. If the number of asynchronous requests is not greater than the maximum number of requests, the loop is interrupted. 4. Check whether the number of hosts in the current asynchronous request exceeds the limit of Host. If not, end the current loop and enter the next loop. 5. Remove the current asynchronous request after the preceding criteria are passed. Add asynchronous requests to an executable collection of asynchronous requests. 7. Add the asynchronous request object to the running asynchronous request collection. Loop through the set of asynchronous requests that can be executed. Create a thread pool and execute the request.
AsyncCall -> executeOn
final class RealCall implements Call {...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);
eventListener.callFailed(RealCall.this, ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if(! success) { client.dispatcher().finished(this); // This call is no longer running!}}}@Override protected void execute(a) {
boolean signalledCallback = false;
timeout.enter();
try {
Response response = getResponseWithInterceptorChain(); // Get interceptor chain
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this.new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response); }}catch (IOException e) {
e = timeoutExit(e);
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e); }}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(a) {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally{ Thread.currentThread().setName(oldName); }}protected abstract void execute(a);
}
Copy the code
Let’s start with the NamedRunnable class:
1. This class inherits the Runnable interface, notifying that the run method is overridden. 2. Define an abstract method execute, which is called in the run method.
Then let’s look at the AsyncCall class:
1. This object inherits from NamedRunnable and overwrites the execute method. It’s important to note that this exectue method is not the execute method called in the example of making a network request using OkHttp in this article. The asynccAll.executeon method is passed in a thread pool that performs the task and passes in the current AsyncCall object. 3. Recall that when we learned about thread pools, the execute method of the thread pool passed a Runnable object and called the run method of the Runnable object. In this case, the parent class of AsyncCall is NamedRunnable, which implements the Runnable interface and overwrites the run method, which in turn calls its abstract excute method, which is implemented in the AsyncCall object. So, in the end, the network request calls to the asynccall.execute method. 4, in this method, we saw a familiar figure getResponseWithInterceptorChain, this method has been analyzed in the above there is no longer to do here.
summary
Now that we’ve analyzed this, let’s make a summary.
1. The difference between synchronous and asynchronous requests is that synchronous requests do not use a thread pool, whereas asynchronous requests are put into a thread pool for execution. 2, synchronous and asynchronous request will eventually call getResponseWithInterceptorChain method for network requests. 3, getResponseWithInterceptorChain method can be carried in turn interceptors, layers will be asked to pass down, get network response opens up again after delivery (as shown in the figure below), the code belongs to the chain of responsibility design pattern.
conclusion
At this point, the okHTTP implementation flow is covered, but for space reasons, information about interceptors in OKHTTP and the design patterns involved will be covered in a future article. My seniority is still young, ability is limited, if the article where write wrong, welcome everybody clap brick, I will be very grateful.
The resources
Wandroid website open API