How much do you know about the use and principle of OkHttp

brief introduction

As the most popular network request underlying framework at present, how to defeat other frameworks and be recognized by the majority of people? Compared with other network frameworks, it has the following advantages:

  • Support gizp compression and decompression of data
  • Support http1 0,http2. 0,SPDY_ 3,QUIC
  • Support network response caching
  • Connection multiplexing
  • Requests at the same address share a socket
  • Retry and redirection mechanism

Compared with other network underlying frameworks, the processing of network request and response is more perfect and professional

How to use

First look at the picture

This figure reflects the basic use process of okhttp. Then we will understand the principle of okhttp according to each step of the figure

I. create OkHttpClient

		private List<Cookie> cookie;
		OkHttpClient client = new OkHttpClient.Builder()   
                .cookieJar(new CookieJar() {  //Cookies are generally used to save user information, web page tracking, user preferences, etc
                    @Override
                    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {  

					// When the request gets a response from the server, call back the method
					//url: requested address
					//coolies: response request header information to be saved
					
				     cookie = cookies;  //Usage - the response can be stored in memory or local files
                    }

                    @Override
                    public List<Cookie> loadForRequest(HttpUrl url) {
                          return cookie;  //In the next network request, the cookie data obtained this time can be passed into the header
                    }
                })
                .cache(new Cache(getCacheDir(),1024*1024)) //Add a cache implementation class and specify the cache file path and size. There is no cache by default
                .addInterceptor(new Interceptor() {  //Add custom interceptor
                    @Override
                    public Response intercept(Chain chain) throws IOException {

                        return chain.proceed(chain.request());
                    }
                })
                .build();

When creating this class, most people are used to directly create a new object to obtain an instance. In fact, in the OkHttpClient construction method, a Builder object is still created to instantiate

1, 1 cookieJar

Cookie is used to store the user's login information, user's preference settings, web browsing tracking, etc. it is stored in the form of key/value pair. It is the data information generated by the server and stored in the client. When the client accesses the website, after carrying the cookie in the request header, the server returns the corresponding user data information according to the information

Considering the security of data, after obtaining the cookie, you can use your own encryption algorithm to save it to prevent hackers from malicious destruction of data Here we just do a simple demonstration
In OKHttp, cookies are stored with the url as the key and the response header as the value. The default initialization is Cookie Jar = cookie jar NO_ COOKIES; (initialized in builder) cookies are not used, so users need to add them manually
Let's see how it is called in the interceptor

 public Response intercept(Chain chain) throws IOException {
 	....
 	List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }
    ....
     HttpHeaders.receiveHeaders(cookieJar, userRequest.url(),networkResponse.headers());
    ....
 }

cookiejar.loadForRequest() is called back when the user sends a request and splices the request header. The "Cookie" header information is added to the request header according to the delivered local cookies

cookie.saveFromResponse() is called back after sending the request and the server responds. It will convert the response header information of the server into a collection callback to the client

public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) {
    if (cookieJar == CookieJar.NO_COOKIES) return;
    List<Cookie> cookies = Cookie.parseAll(url, headers);
    if (cookies.isEmpty()) return;
    cookieJar.saveFromResponse(url, cookies);
  }

The above code is executed in the interceptor BridgeInterceptor of OkHttp

1, 2 cache

OkHttp has its own caching mechanism. When the user requests the server, it will store the cache locally while successfully obtaining the response. When requesting the network next time, if it hits and the cache is not expired or modified, OkHttp will read the local cache file and return it to the client, which can greatly increase the operation efficiency of the program and reduce the pressure on the server It should be noted that the cache is not enabled by default in OkHttp. If you want to use the cache mechanism, you need to use okhttpclient Add cache(newCache(getCacheDir(),1024*1024)) in builder to add cache manually. getCacheDIr() is the cache storage path, and parameter 2 is the size of file storage, in bit

  Request request = new Request.Builder()
                .cacheControl(CacheControl.FORCE_CACHE)
                .url("https://xxx/408/1/json.....")
                .get()
                .build();
        client.newCall(request).enqueue(new okhttp3.Callback() {
        ...
        //ellipsis
        }

We randomly find a json data website, write a request, send it, run the program, and after successful access, it will be in data - > data,

File directory of the project package - > cache file directory, you can see three files

After saving to local

Open three files separately

The first file is 365350f3bc4919e77e578fbaa9b5a942 0

It can be seen from the picture that the file stores a complete response header. The file name is named by adding. 0 after encoding the url

Document II 365350f3bc4919e77e578fbaa9b5a942 one

As shown in the figure, the file stores the response body data returned by the server. The file name is also named after url encoding, but XXX is added one
File 3 journal

This is the cached log file, which stores the current cached version, the number of cached files and operations on the cache. DIRTY,CLEAN, etc. process the cache file for DiskLruCache. The states of creating / modifying the file and successful cache operation are respectively. In addition, there are two states, REMOVE, which indicates that the corresponding cache file is deleted and the READ cache file is accessed

Request request = new Request.Builder()
                .cacheControl(CacheControl.FORCE_NETWORK)
                .url("https://xxx/408/1/json").build();

In the request request, we can also control the Cache through parameters. By default, two Cache instances are provided in CacheControl

  • public static final CacheControl FORCE_NETWORK = new Builder().noCache().build(); Force network access before each cache use
  • public static final CacheControl FORCE_CACHE = new Builder() .onlyIfCached() .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS) .build(); If there is a cache, the cache is forced to be used, otherwise 504 is returned

Readers can learn about CacheControl separately. This is the attribute in the header of the request, which corresponds to multiple value values. The client can control the cache by adding the corresponding value in the header

Through the above, we recognize the basic use of OkHttp cache. In subsequent articles, bloggers will analyze OkHttp cache mechanism in detail from the perspective of source code

1, 3 Interceptor custom interceptor

OkHttp uses interceptors to process network requests and responses. There are five default interceptors in OkHttp

  • RetryAndFollowUpInterceptor: (retry and redirect). If the network response time is long, the route is blocked, and there are problems with the network connection flow, it will try to retry. When the server returns the response code beginning with 3 with the new address of the target resource, the client will redirect according to the address returned by the server
  • BridgeInterceptor: (bridge interceptor) is used to improve the request body of the client, synthesize a complete header information, store it in the request, and parse the response data information returned by the server
  • CacheInterceptor: (CACHE interceptor), which is used to cache the response returned by the server to the local, so that it can be obtained directly from the local when accessing the address next time
  • ConnectInterceptor: (connection interceptor) establishes a connection with the server. The connection can be reused through the socket connection pool to reduce the time-consuming operation of tcp connection handshake
  • CallServerInterceptor: (read / write interceptor) performs io interaction with the server, and actually performs data interaction with the server

We are going to explain the interceptor in the next article Here, addInterceptor() is to add a user-defined interceptor to the interceptor chain. Its purpose is to add users' own operations before the execution of the built-in interceptor, and operate the request and response according to their own needs. The corresponding addNetWorkInterceptor() is also different in the execution order. The former is executed before the execution of the built-in interceptor, The latter is called after the execution of ConnectInterceptor. The order difference leads to its different nature, interest and understanding, not very common.
The above is the creation of OkHttpClient. Let's continue to see request

II. Create request

 Request request = new Request.Builder()
                .cacheControl(CacheControl.FORCE_NETWORK)
.url("https://zzz/list/408/1/json").build(); / / original request

Create a request and use the builder mode. You can add header,post,put and other parameters. For the header, we do not declare parameters, so the bridge interceptor will automatically add the required request header information to the request, improve the header information, and finally build to obtain the request object

III. create RealCall object

client. Execute RealCall within the newcall (request) method Newrealcall() initializes a RealCall object

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }

After following up the source code, we can see that a RetryAndFollowUpInterceptor retry redirection interceptor is created in the RealCall construction method, and the client,request and whether to use WebSocket are stored in variables

WebSocket is a two-way channel based on TCP connection. Its advantage is that it can reduce the connection overhead between the client and the server, that is, it only needs one handshake to establish a persistent connection and carry out two-way data transmission At the same time, compared with the general TCP, the control overhead is less. When the client and the server exchange data, the protocol contains fewer header packets, and only needs to carry 2 ~ 10 bytes of header, while the http protocol needs to carry complete header information for each communication

IV. execute enqueue() method

	client.newCall(request).enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {              
            }
            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
            }
        });

In the third point, after obtaining the RealCall object, it then calls its internal enqueue() method. This method sends an asynchronous request and its corresponding synchronous request method execute(). The main difference between the two is that the former uses the Thread created by the Thread pool to execute the network request, while the latter directly sends the network request in the main Thread, Therefore, if you want to use it, you need to manually add a Thread, because the UI Thread does not allow these time-consuming operations to be executed

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
    //... 
    eventListener.callStart(this);  //Callback after request sending
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
  

enqueue() of dispatcher() is called. First, let's talk about dispatcher

Scheduler, as its name suggests, is a class used to control and manage threads. It internally maintains three two-way queues, readyAsyncCalls waiting queue. When the maximum number of requests exceeds, it will temporarily store tasks in this queue and wait. runningAsyncCalls running queue is used to store running tasks. When the task execution ends, it will be recycled in this class, that is, out of the queue, At the same time, get the task from the waiting queue to continue execution The first two queues are used for asynchronous execution, while runningSyncCalls is used for synchronous request execute() method. It can be found that AsynCall is used for asynchrony, which is different from synchronization. Finally, we will briefly introduce this class
Next, look at the enqueue() method in the scheduler dispatcher

 synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {//1
      runningAsyncCalls.add(call);//2
      executorService().execute(call);//3
    } else {
      readyAsyncCalls.add(call);//4
    }
  }
  1. In OkHttp, the maximum number of requests allowed by default is 64 and the maximum number of requests to access the same host is 5. Of course, this value can also be modified by the client First, judge whether the number of currently running requests is within the maximum range
  2. If the number of requests does not reach the maximum, it is added to the runningAsyncCalls queue
  3. Execute executorservice() execute(call); Executorservice() returns a thread pool object:
  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;  

It returns a 0 core thread, a non core thread with the maximum int value and a SynchronousQueue queue. Each thread has 60 seconds of idle time. The advantage of this is that when a task enters, there is no need to wait and queue, and a thread can be quickly created to execute this task. This prevents some tasks from not executing after queuing for a long time. About thread pool ThreadPoolExecutor , I have analyzed it in detail in my previous articles. Interested readers can go and have a look
Finally, we call the executor() method of the thread pool to perform this task.

  1. If the maximum number of requests or host accesses is exceeded, the task will be placed in the readyAsyncCalls queue and wait

Generally speaking, the logic is relatively simple. First, the RealCall object is created, then the internal enqueue() is called and a callback callback interface is passed in. In enqueue(), an AsyncCall object is passed in through enqueue() of OkHttp scheduler. Later, we add a task to the queue in enqueue(), and create a thread pool to execute the incoming call task, It's over. What about the callback passed in by the client? Where did you call it back?

4, 1 Call object

We left a part of AsyncCall above and didn't say that this class is an internal class of RealCall object, which is indirectly inherited from Runnable object. Is that clear? Since it inherits from the Runnable object, there must be a run() method inside. When the thread pool executes a task, it will call back this run() method

@Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }
  //Abstract method
  protected abstract void execute();

In AsyncCall, we only found the execute() method, while we found its run() in its direct parent class NamedRunnable. This class is an abstract class. There is only one run method and one abstract method execute(), which are called in run(). Isn't it the AsyncCall class that directly inherits this class, and doesn't it happen to have this method?
OK, now let's sort out the idea. When it comes to dispatcher When enqueue (New asynccall (responsecallback)) method is used, the call task is internally executed through the thread pool. Specifically, it calls back the run() (because this class is a Runnable) method of the AsynCall object. Since AsynCall itself does not have run(), it calls the run() method of the parent class NamedRunnable, and internally calls back the execute() method of the child class. Let's see

protected void execute() {
//.. Omitted
      try {
        //Interceptor call, the core method of OkHttp network connection and interaction
        Response response = getResponseWithInterceptorChain();
        
        if (retryAndFollowUpInterceptor.isCanceled()) {//Network retry or redirection cancellation indicates network access failure and callback failure method
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
        //Otherwise, a response is returned
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
       }catch (IOException e) {
       //An exception occurred while requesting the network, and the callback failed
 		  eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
		}
//		.... ellipsis
        finally {
        //Finally, call the scheduler to reclaim this call object. If there are tasks in the waiting queue, continue to the next call
        client.dispatcher().finished(this);
      }
}

The getResponseWithInterceptorChain() method is the core of OkHttp's interaction with the server. It internally calls the interceptors described above, which will be described in the following article. responseCallback is the callback interface passed in by the client, If you retry or redirect and successfully access the network, or if you do not retry or redirect and successfully access the network, you will get a response callback. The callback object passes in a response

Simply mention the retry redirection. The retry is timeout when accessing the network. If the route fails or an exception occurs in the connection flow after establishing a remote connection, you will try to resend the request. It is still useless after performing the default limited retry operation. The network access fails. The redirection is the success of accessing the server, but the address of the target resource has been migrated, Therefore, the server returns the migrated new address through the response header. After the client gets the new address, it re initiates the request and obtains data resources, such as the client requests to use it“ http://xxx "Access the target server, and the server resource address has been converted to" https: / / ", the server will return the converted new address

summary

In this paper, we describe the principle of OkHttp based on its basic use, including cache, cookie jar, introduction to interceptor and RealCall object This paper only introduces enqueue() asynchronous request. The synchronous request of OkHttp is much simpler than asynchronous. It directly calls the execute() method of RealCall object internally, which does not involve dispatcher scheduler and thread pool. The content of the method is similar to the execute() method in AsynCall. There is no need to elaborate the scheduler management This article only analyzes the usage principle of OkHttp in a superficial way. If you want to really understand the underlying layer of OkHttp, you need to carefully study its getResponseWithInterceptorChain(), and then the source code analysis of interceptors will be updated If there is anything wrong in the article, please correct it

Keywords: Android network

Added by phpmoron on Thu, 27 Jan 2022 01:32:38 +0200