• OkHttp原理分析总结


    Okhttp 介绍

    OkHttp 是 Square 公司开源的一款网络框架,封装了一个高性能的 http 请求库。

    https://github.com/square/okhttp

    特点

    • 支持 spdy、http2.0、websocket 等协议
    • 支持同步、异步请求
    • 封装了线程池,封装了数据转换,提高性能。
    • 在 Android 6.0 中自带的网络请求 API 的底层就是使用了 okhttp 来进行的
    • 使用 okhttp 比较接近真正的 HTTP 协议的框架

    Okhttp 中几个重要类的介绍

    OkHttpClient

    这个类主要是用来配置 okhttp 这个框架的,通俗一点讲就是这个类是管理这个框架的各种设置的。

    Call 类的工厂,通过 OkHttpClient 才能得到 Call 对象。

    OkHttpClient 使用注意

    OkHttpClient 应该被共享,使用 okhttp 这个框架的时候,最好要将 OkHttpClient 设置成单例模式,所有的 HTTP 在进行请求的时候都要使用这一个 Client 。因为每个 OkHttpClient 都对应了自己的连接池和线程池。减少使用连接池和线程池可以减少延迟和内存的使用。相反的如果每个请求都创建一个 OkHttpClient 的话会很浪费内存资源。

    OkHttpClient的创建

    OkHttpClient 有三个创建方法

    第一个方法:直接使用 new OkHttpClient() 来创建一个实例对象就可以了,这个实例对象有默认的配置。默认请求连接超时时间 10 s ,读写超时时间 10 s,连接不成功会自动再次连接。

    第二个方法:就是通过 Builder的方式来自己定义一个 OkHttpclient 。当然如果你直接 build 没有自己配置参数的话,效果和第一个方法是一样的。

    public final OkHttpClient = new OkHttpClient.Builder()
      .addInterceptor(new HttpLoggingInterceptor())
      .cache(new Cache(cacheDir,cacheSize))
      .等等配置
      .build();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    第三个方法:就是通过已有的 OkHttpClient 对象来复制一份共享线程池和其他资源的 OkHttpClient 对象。

    OkHttpClient agerClient = client.newBuilder()
      .readTimeout(500,TimeUnit.MILLSECONS)
      .build();
    
    • 1
    • 2
    • 3

    这种方法的好处就是,当我们有一个特殊的请求,有的配置有点不一样,比如要求连接超过 1 s 就算超时,这个时候我们就可以使用这个方法来生成一个新的实例对象,不过他们共用很多其他的资源,不会对资源造成浪费。

    关于 OkHttpClient 的配置改变都在 Builder 中进行

    不需要了可以关闭

    其实持有的线程池和连接池将会被自定释放如果他们保持闲置的话。

    你也可以自动释放,释放后将来再调用 call 的时候会被拒接。

    client.dispatcher().excurorService().shutdown()
    
    • 1

    清除连接池,注意清除后,连接池的守护线程可能会立刻退出。

    client.connectionPool().evictAll()
    
    • 1

    如果 Client 有缓存,可以关闭。注意:再次调用一个被关闭的 cache 会发生错误。也会造成 crash。

    client.cache().close();
    
    • 1

    OkHttp 在 HTTP/2 连接的时候也会使用守护线程。他们闲置的时候将自动退出。

    知道有这么一回事就行,一般不会主动调用。

    Call 类

    Call 这个类就是用来发送 HTTP 请求和读取 HTTP 响应的一个类

    image-20221108142020997

    这个类的方法很少,从上到下依次是:放弃请求、异步执行请求、同步执行请求。

    Request 类

    这个类就是相当于 http 请求中的请求报文,是用来表达请求报文的,所以这里可以设置请求的 url、请求头、请求体等等和请求报文有关的内容。

    主要方法罗列:

    // 获取请求 url
    public HttpUrl url();
    // 获取请求方法类型
    public String method();
    // 获取请求头
    public Headers headers();
    //获取请求体
    public RequestBody body();
    // 获取 tag
    public Object tag();
    // 返回缓存控制指令,永远不会是 null ,即使响应不包含 Cache-Control 响应头
    public CacheControl cacheControl();
    // 是否是 https 请求
    public boolean isHttps();
    // Resquest{method=" ",url=" ",tag = " "}
    public String toString();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    image-20221108142440493

    这是它的 Builder 中提供的方法,只设置 .url() 的时候默认是 post 请求。

    RequestBody

    介绍完请求报文就要介绍请求体了,这都是和 http协议紧密联系的。

    RequestBody 就是用来设置请求体的,它的主要方法就是下面这个几个静态方法,用来生成对应的请求体:

    在这里插入图片描述

    就是通过这几个方法来产生对应的不同的请求体。MediaType 是用来描述请求体或者响应体类型的。比如请求体类型是 json 串格式的,那对应的 MediaType 就是MediaType.parse("application/json; charset=utf-8"); ,如果上传的是文件那么对应的就是 application/octet-stream,还有几个常用的类型 text/plain imge/png text/x-markdown 等等。

    它还有两个子类:

    在这里插入图片描述

    FormBody 这个请求体是我们平时最常用的,就是我们平时使用 post 请求的时候,参数是键值对的形式。就是使用这个请求体最简单了。

    说深一点,对应的请求报文是:

    POST /test HTTP/1.1   请求行
    Host: 32.106.24.148:8080  下面都是请求头
    Content-Type: application/x-www-form-urlencoded 用于指明请求体的类型。
    User-Agent: PostmanRuntime/7.15.0
    Accept: */*
    Cache-Control: no-cache
    Postman-Token: 954bda0d-dbc2-4193-addf-a7631cab2cfa,5ba2ebed-90b4-4f35-bcf5-80c4777de471
    Host: 39.106.24.148:8080
    accept-encoding: gzip, deflate
    content-length: 133
    Connection: keep-alive
    cache-control: no-cache
    
    key0=value0&key1=value1  请求体(也是我们的参数)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这是发送的原始的报文格式,用代码实现的话就是

    // 创建客户端
    OkHttpClient client = new OkHttpclient();
    // 建立请求体 
    FormBody formBody = new FormBody.Builder()
                        .add("key0", "value0")
                        .add("key1","value1")
                        .build();
    // 建立请求报文
    Request request = new Request.Builder
                                    .post(formBody)
                                    .url("请求url")
                                    .addHeader("Content-Type", "application/x-www-form-urlencoded")
      .addHeader("User-Agent", "PostmanRuntime/7.15.0")
      .addHeader("Accept", "*/*")
      .addHeader("Cache-Control", "no-cache")
      .addHeader("Postman-Token", "954bda0d-dbc2-4193-addf-a7631cab2cfa,af7c027c-a7ba-4560-98ae-3a2a473ab88a")
      .addHeader("Host", "39.106.24.148:8080")
      .addHeader("accept-encoding", "gzip, deflate")
      .addHeader("content-length", "133")
      .addHeader("Connection", "keep-alive")
      .addHeader("cache-control", "no-cache")
      .build();
    // 发起请求
    client.newCall(request).excute();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    上面是使用了 FormBody 的形式,如果使用 RequestBody 的话就要更麻烦一些。

    OkHttpClient client = new OkHttpClient();
    
    MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
    RequestBody body = RequestBody.create(mediaType, "key0=value0&key1=value1");
    Request request = new Request.Builder()
      .url("http://39.106.24.148:8080/test")
      .post(body)
      .addHeader("Content-Type", "application/x-www-form-urlencoded")
      .addHeader("User-Agent", "PostmanRuntime/7.15.0")
      .addHeader("Accept", "*/*")
      .addHeader("Cache-Control", "no-cache")
      .addHeader("Postman-Token", "954bda0d-dbc2-4193-addf-a7631cab2cfa,af7c027c-a7ba-4560-98ae-3a2a473ab88a")
      .addHeader("Host", "39.106.24.148:8080")
      .addHeader("accept-encoding", "gzip, deflate")
      .addHeader("content-length", "133")
      .addHeader("Connection", "keep-alive")
      .addHeader("cache-control", "no-cache")
      .build();
    Response response = client.newCall(request).execute();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    当然平时我们使用的时候,不用拼上这么多的请求头,我这样写的目的就是为了更加还原请求报文。

    还有一个子类 MultipartBody这个可以用来构建比较复杂的请求体。

    1995 年 Content-Type 的类型扩充了 multipart/form-data 用来支持向服务器发送二进制数据。如果一次提交多种类型的数据,比如:一张图片和一个文字,这个时候引入了 boundaryboundary使得 POST 可以满足这种提交多种不同的数据类型。通过 boundary 可以实现多个不同类型的数据同时存在在一个 Request 中。两个 boundary之间就是一个类型的数据,并且可以重新设置 Content-Type

    与 HTML 文件上传形式兼容。每块请求体都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求。例如,他们的 Content-Disposition。如果 Content-Length 和 Content-Type 可用的话,他们会被自动添加到请求头中。

    来看一下这种类型的请求报文是什么样的:

    POST /web/UploadServlet HTTP/1.1
    Content-Type: multipart/form-data; boundary=e1b05ca4-fc4e-4944-837d-cc32c43c853a
    Content-Length: 66089
    Host: localhost.tt.com:8080
    Connection: Keep-Alive
    Accept-Encoding: gzip
    User-Agent: okhttp/3.5.0
    
    –e1b05ca4-fc4e-4944-837d-cc32c43c853a
    Content-Disposition: form-data; name=”file”; filename=”**.png”
    Content-Type: image/png
    Content-Length: 65744
    
    fdPNG
    IHDR�0B7M�iM�M�CCPIM�CC ProfileH��……………………IEND�B`�
    –e1b05ca4-fc4e-4944-837d-cc32c43c853a
    Content-Disposition: form-data; name=”comment”
    Content-Length: 30
    
    上传一个图
    –e1b05ca4-fc4e-4944-837d-cc32c43c853a–
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    第一个数据是一张 png 的图,重新设置了 Content-Type:image/png 中间的乱码就是图片的数据。这一堆数据前有一个空行,表示上下分别是请求头、请求体。

    第二个数据,就是一个文本数据。

    这样它们一起构成了请求体。

    讲起来可能比较复杂,就记住,当既需要上传参数,又需要上传文件的时候用这种请求体。

    MediaType mediaType = MediaType.parse("image/png");
            RequestBody requestBody = new MultipartBody.Builder()
                        // 需要设置成表单形式否则无法上传键值对参数
                    .setType(MultipartBody.FORM)
                    .addPart(Headers.of("Content-Disposition", "form-data;name=\"title\""),
                            RequestBody.create(null, "Square Logo"))
                    .addPart(
                            Headers.of("Content-Disposition", "form-data;name=\"imge\""),
                            RequestBody.create(mediaType, new File("路径/logo.png"))
                    ).
                            build();
            Request request = new Request.Builder()
                    .post(requestBody)
                    .url("https://api.imgur.com/3/image")
                    .build();
            try {
                mOkHttpClient.newCall(request).execute();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    简化写法:

    MediaType mediaType = MediaType.parse("image/png");
            RequestBody requestBody = new MultipartBody.Builder()
                     .setType(MultipartBody.FORM)
                   .addFormDataPart("title","logo")
                    .addFormDataPart("img","logo.png",RequestBody.create(mediaType,new File("路径/logo.png")))
                    .build();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Content-Disposition 可以用在消息体的子部分中,用来给出其对应字段的相关信息。作为 multipart body 中的消息头,第一个参数总是固定不变的 form-data; 附加的参数不区分大小写,并且拥有参数值,参数名与参数值用等号连接,参数之间用分号分隔。参数值用双引号括起来

    // 比如这样,就是这种固定的格式
    "Content-Disposition","form-data;name=\"mFile\";filename=\"xxx.mp4\""
    
    • 1
    • 2

    到这里关于请求的几个重要的类就讲完了。

    总结一下

    只要掌握 http 请求的原理,使用起 okhttp 来也就不是什么问题了。

    首先 OkHttpClient 是用来设置关于请求工具的一些参数的,比如超时时间、是否缓存等等。

    Call 对象是发起 Http 请求的对象,通过 Call 对象来发起请求。

    发起请求的时候,需要有请求报文,Request 对象就是对应的请求报文,可以添加对应的请求行、请求头、请求体。

    说起请求体就是对应了 RequestBody 了。然后这个网络请求过程就完成了!

    OKHTTP架构图

    OKHTTP架构图

    OKHttp发送主体流程

    image-20221108145253547

    在使用OkHttp发起一次请求时,对于使用者最少存在OkHttpClient、Request与Call三个角色。其中OkHttpClient和Request的创建可以使用它为我们提供的Builder(建造者模式)。而Call则是把Request交给OkHttpClient之后返回的一个已准备好执行的请求。

    同时OkHttp在设计时采用的门面模式,将整个系统的复杂性给隐藏起来,将子系统接口通过一个客户端OkHttpClient统一暴露出来。

    OkHttpClient中全是一些配置,比如代理的配置、ssl证书的配置等。而Call本身是一个接口,我们获得的实现为:RealCall

    static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        // Safely publish the Call instance to the EventListener.
        RealCall call = new RealCall(client, originalRequest, forWebSocket);
        call.eventListener = client.eventListenerFactory().create(call);
        return call;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Callexecute代表了同步请求,而enqueue则代表异步请求。两者唯一区别在于一个会直接发起网络请求,而另一个使用OkHttp内置的线程池来进行。这就涉及到OkHttp的任务分发器。

    • Call: 每一个请求的实例,比如登录login 对应一个Call、获取用户信息 对应一个Call。Call本身就是一个接口,用户的每一个Http请求就是一个Call实例,而且每一个Call都对应一个线程。
      Call包含了 request()、execute()、enqueue() 方法。

    • RealCall: 具体的Call接口实现类,代表每一个HTTP请求。每一个RealCall内部有一个AsyncCall final类。

    • AsyncCall: RealCall类的内部final类,实现了NamedRunnable类的execute()。继承于NamedRunnable类,NamedRunnable类实现了Runnable接口,并且有一个execute()抽象方法,这个抽象方法在Runnable的run()里执行。

    • Dispatcher:

      • OkHttp的任务队列,其内部维护了一个线程池,进行线程分发,实现非阻塞,高可用,高并发。
        当有接收到一个Call时,Dispatcher负责在线程池中找到空闲的线程并执行其execute方法。
      • Okhttp采用Deque作为缓存队列,按照入队的顺序先进先出。
      • OkHttp最出彩的地方就是在try/finally中调用了finished函数,可以主动控制等待队列的移动,而不是采用 锁或者wait/notify,极大减少了编码复杂性。

    分发器

    Dispatcher,分发器就是来调配请求任务的,内部会包含一个线程池。可以在创建OkHttpClient时,传递我们自己定义的线程池来创建分发器。

    这个Dispatcher中的成员有:

    //异步请求同时存在的最大请求
    private int maxRequests = 64;
    //异步请求同一域名同时存在的最大请求
    private int maxRequestsPerHost = 5;
    //闲置任务(没有请求时可执行一些任务,由使用者设置)
    private @Nullable Runnable idleCallback;
    
    //异步请求使用的线程池
    private @Nullable ExecutorService executorService;
    
    //异步请求等待执行队列
    private final Deque readyAsyncCalls = new ArrayDeque<>();
    
    //异步请求正在执行队列
    private final Deque runningAsyncCalls = new ArrayDeque<>();
    
    //同步请求正在执行队列
    private final Deque runningSyncCalls = new ArrayDeque<>();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    同步请求

    synchronized void executed(RealCall call) {
    	runningSyncCalls.add(call);
    }
    
    • 1
    • 2
    • 3

    因为同步请求不需要线程池,也不存在任何限制。所以分发器仅做一下记录。

    异步请求

    synchronized void enqueue(AsyncCall call) {
    	if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) 	  {
    		runningAsyncCalls.add(call);
    		executorService().execute(call);
    	} else {
    		readyAsyncCalls.add(call);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    当正在执行的任务未超过最大限制64,同时runningCallsForHost(call) < maxRequestsPerHost同一Host的请求不超过5个,则会添加到正在执行队列,同时提交给线程池。否则先加入等待队列。

    加入线程池直接执行没啥好说的,但是如果加入等待队列后,就需要等待有空闲名额才开始执行。因此每次执行完一个请求后,都会调用分发器的finished方法

    //异步请求调用
    void finished(AsyncCall call) {
    	finished(runningAsyncCalls, call, true);
    }
    //同步请求调用
    void finished(RealCall call) {
    	finished(runningSyncCalls, call, false);
    }
    
    private  void finished(Deque calls, T call, boolean promoteCalls) {
    	int runningCallsCount;
    	Runnable idleCallback;
    	synchronized (this) {
            //不管异步还是同步,执行完后都要从队列移除(runningSyncCalls/runningAsyncCalls)
    		if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    		if (promoteCalls) promoteCalls();
            //异步任务和同步任务正在执行的和
    		runningCallsCount = runningCallsCount();
    		idleCallback = this.idleCallback;
    	}
    	// 没有任务执行执行闲置任务
    	if (runningCallsCount == 0 && idleCallback != null) {
    		idleCallback.run();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    需要注意的是 只有异步任务才会存在限制与等待,所以在执行完了移除正在执行队列中的元素后,异步任务结束会执行promoteCalls()。很显然这个方法肯定会重新调配请求。

    private void promoteCalls() {
        //如果任务满了直接返回
    	if (runningAsyncCalls.size() >= maxRequests) return; 
        //没有等待执行的任务,返回
    	if (readyAsyncCalls.isEmpty()) return; 
        //遍历等待执行队列
    	for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
    		AsyncCall call = i.next();
            //等待任务想要执行,还需要满足:这个等待任务请求的Host不能已经存在5个了
    		if (runningCallsForHost(call) < maxRequestsPerHost) {
    			i.remove();
    			runningAsyncCalls.add(call);
    			executorService().execute(call);
    		}
    
    		if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    请求流程

    用户是不需要直接操作任务分发器的,获得的RealCall中就分别提供了executeenqueue来开始同步请求或异步请求。

    @Override public Response execute() throws IOException {
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        captureCallStackTrace();
        eventListener.callStart(this);
        try {
          //调用分发器
          client.dispatcher().executed(this);
          //执行请求
          Response result = getResponseWithInterceptorChain();
          if (result == null) throw new IOException("Canceled");
          return result;
        } catch (IOException e) {
          eventListener.callFailed(this, e);
          throw e;
        } finally {
          //请求完成
          client.dispatcher().finished(this);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    异步请求的后续同时是调用getResponseWithInterceptorChain()来执行请求

    @Override
    public void enqueue(Callback responseCallback) {
    	synchronized (this) {
    		if (executed) throw new IllegalStateException("Already Executed");
    		executed = true;
    	}
    	captureCallStackTrace();
    	eventListener.callStart(this);
        //调用分发器
    	client.dispatcher().enqueue(new AsyncCall(responseCallback));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果该RealCall已经执行过了,再次执行是不允许的。异步请求会把一个AsyncCall提交给分发器。

    AsyncCall实际上是一个Runnable的子类,使用线程启动一个Runnable时会执行run方法,在AsyncCall中被重定向到execute方法:

    final class AsyncCall extends NamedRunnable {
    	private final Callback responseCallback;
    
    	AsyncCall(Callback responseCallback) {
    		super("OkHttp %s", redactedUrl());
    		this.responseCallback = responseCallback;
    	}
    
        //线程池执行
    	@Override
    	protected void execute() {
    	 boolean signalledCallback = false;
          try {
            Response response = getResponseWithInterceptorChain();
           //.......
          } catch (IOException 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() {
            String oldName = Thread.currentThread().getName();
            Thread.currentThread().setName(name);
            try {
                execute();
            } finally {
                Thread.currentThread().setName(oldName);
            }
        }
    
        protected abstract void execute();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    同时AsyncCall也是RealCall的普通内部类,这意味着它是持有外部类RealCall的引用,可以获得直接调用外部类的方法。

    可以看到无论是同步还是异步请求实际上真正执行请求的工作都在getResponseWithInterceptorChain()中。这个方法就是整个OkHttp的核心:拦截器责任链。但是在介绍责任链之前,我们再来回顾一下线程池的基础知识。

    分发器线程池

    前面我们提过,分发器就是来调配请求任务的,内部会包含一个线程池。当异步请求时,会将请求任务交给线程池来执行。那分发器中默认的线程池是如何定义的呢?为什么要这么定义?

    public synchronized ExecutorService executorService() {
        if (executorService == null) {
          executorService = new ThreadPoolExecutor(
              					0,   				//核心线程
                                Integer.MAX_VALUE,  //最大线程
                                60,					//空闲线程闲置时间
                                TimeUnit.SECONDS,	//闲置时间单位
                                new SynchronousQueue(), //线程等待队列
                                Util.threadFactory("OkHttp Dispatcher", false) //线程创建工厂
          );
        }
        return executorService;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    为什么选择使用OKHttp

    • 1.可扩展性高。类似于缓存,Dns,请求/连接/响应超时时间等等都可以通过配置传入,甚至线程池都可以根据自己的需求来配置。
    • 2.OKHttp使用了连接池缓存,提高通信效率。
    • 3.责任链五层拦截器模式,每层功能清晰明了,并且提供了两层可扩展的拦截器方便进行所需要的改造。
    • 4.层次结构清晰,方便进行问题的排查。
    • 5.观察者模式的充分使用,查看请求状态和监控请求状态变得十分简单。
    • 6.使用了OKIO框架进行数据的处理,效率和安全性上更高。

    参考

    一个极简的Http请求client推荐,一行搞玩外部请求

  • 相关阅读:
    3.DesignForVias\2.AutoRoutingSecondStep
    【zookeeper】zookeeper集群安装
    英语字典的一些 关键字 解释:
    [GO]、数组与切片
    波士顿房价预测分析----以线性回归LinearRegression为例
    Creo9.0 特征的成组
    el-dialog固定高度
    【项目方案】利用Zookeeper实现集群缓存一致
    基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录
    再次捕获!重保期间拦截针对Coremail的钓鱼攻击
  • 原文地址:https://blog.csdn.net/agonie201218/article/details/127751190