<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      [計算機網絡/HTTP/網絡請求] Okhttp: 網絡請求框架

      • 準備改造一套數據源連接框架,需要借助OkHttp,故記錄之。

      本文主要是轉載第1篇參考文獻。

      概述:Okhttp

      • Android中的網絡請求框架,基本是okhttpRetrofit一統天下,而Retrofit又是以okhttp為基礎。

      所以,系統學習okhttp的使用和原理就很有必要了。

      • okhttp是由square公司開發,Android中公認最好用的網絡請求框架,在接口封裝上做的簡單易用,

      • 它有以下默認特性:

      • 支持HTTP/2,允許所有同一個主機地址的請求共享同一個socket連接
      • 使用連接池減少請求延時
      • 透明的GZIP壓縮減少響應數據的大小
      • 緩存響應內容,避免一些完全重復的請求
      • 當網絡出現問題的時候OkHttp 會自動恢復一般的連接問題,如果你的服務有多個IP地址,當第一個IP請求失敗時,OkHttp會交替嘗試你配置的其他IP。

      安裝指南

      Maven依賴

      implementation 'com.squareup.okhttp3:okhttp:3.14.9'
      implementation 'com.squareup.okio:okio:1.17.5'
      

      其中Okio庫是對Java.iojava.nio的補充,以便能夠更加方便,快速的訪問、存儲和處理你的數據。
      OkHttp的底層使用該庫作為支持。
      另外,如果是在Android上運行,別忘了申請網絡請求權限,如果還使用網絡請求的緩存功能,那么還要申請讀寫外存的權限:

      <uses-permission android:name="android.permission.INTERNET" />
      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
      <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
      

      使用指南

      get請求

      • get方式中又可以分為2種情況:同步請求異步請求

      • 同步請求在進行請求的時候,當前線程會被阻塞住,直到得到服務器的響應后,后面的代碼才會執行;

      • 異步請求不會阻塞當前線程,它采用了回調的方式,請求是在另一個線程中執行的,不會影響當前的線程。

      • 以百度主頁為例,進行Get請求:

      同步請求

      import okhttp3.Request;
      import okhttp3.Call;
      import okhttp3.OkHttpClient;
      
      public void getSync() {
      	//同步請求
      	OkHttpClient httpClient = new OkHttpClient();
      	String url = "https://www.baidu.com/";
      	Request getRequest = new Request.Builder()
      			.url(url)
      			.get()
      			.build();
      	//準備好請求的Call對象
      	Call call = httpClient.newCall(getRequest);//只是配置請求,不會觸發網絡操作 (需確保每個 Call 對象只執行一次,避免重復使用)
      	try {
      		Response response = call.execute();//必須調用 execute() 或 enqueue() 才會真正發起請求
      		Log.i(TAG, "okHttpGet run: response:"+ response.body().string());
      	} catch (IOException e) {
      		e.printStackTrace();
      	}
      }
      
      • 首先,創建了OkHttpClient實例,接著用Request.Builder構建了Request實例、并傳入了百度主頁的url
      • 然后,httpClient.newCall方法傳入Request實例生成call,最后在子線程調用call.execute()執行請求獲得結果response

      使用OkHttp進行get請求,是比較簡單的,只要在構建Request實例時更換url就可以了。

      異步請求

      • call.execute()同步方法
      • 想要在主線程直接使用可以嘛?當然可以,使用call.enqueue(callback)即可
      public void getAsync() {
      	//異步請求
      	OkHttpClient httpClient = new OkHttpClient();
      	String url = "https://www.baidu.com/";
      	Request getRequest = new Request.Builder()
      			.url(url)
      			.get()
      			.build();
      	//準備好請求的Call對象
      	Call call = httpClient.newCall(getRequest);
      	call.enqueue(new Callback() {
      		@Override
      		public void onFailure(Call call, IOException e) {
      		}
      
      		@Override
      		public void onResponse(Call call, Response response) throws IOException {
      			Log.i(TAG, "okHttpGet enqueue: onResponse:" + response.body().string());
      			ResponseBody body = response.body();
      			String string = body.string();
      			byte[] bytes = body.bytes();
      			InputStream inputStream = body.byteStream();
      		}
      	});
      }
      
      • call.enqueue會異步執行,需要注意的是,2個回調方法onFailure、onResponse是執行在子線程的

      所以,如果想要執行UI操作,需要使用Handler或其他方式切換到UI線程。

      取消請求

      • 每一個Call對象只能執行一次

      如果想要取消正在執行的請求,可以使用call.cancel(),通常在離開頁面時都要取消執行的請求的。

      結果處理

      • 請求回調的兩個方法是指 傳輸層 的失敗和成功。
      • onFailure通常是connection連接失敗或讀寫超時;
      • onResponse是指成功得從服務器獲取到了結果,但是這個結果的響應碼可能是404、500等,也可能就是200(response.code()的取值)。
      • 如果response.code()200,表示應用層請求成功了。

      此時我們可以獲取Response的ResponseBody,這是響應體。
      從面看到,可以從ResponseBody獲取string、byte[ ]、InputStream,這樣就可以對結果進行很多操作了。
      比如UI上展示string(要用Handler切換到UI線程)、通過InputStream寫入文件等等。

      上面異步請求執行后 結果打印如下:

      okHttpGet run: response:<!DOCTYPE html> <!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css 
      href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> 
      <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新聞</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地圖</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>視頻</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>貼吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登錄</a> </noscript> <script>document.write('<a + encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登錄</a>');                                                                                                                   </script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多產品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>關于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必讀</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意見反饋</a>&nbsp;京ICP證030173號&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
      

      post請求

      • POST請求將參數放在請求的主體中,不會直接顯示在URL中。Post請求也分為同步和異步方式,和get方式用法相同

      同步POST

      public void postSync(){//同步請求
      	new Thread(new Runnable() {
      		@Override
      		public void run() {
      			OkHttpClient okHttpClient = new OkHttpClient();
      			FormBody formBody = new FormBody.Builder()
      					.add("a","1")
      					.add("b","2")
      					.build();
      			Request request=new Request.Builder()
      					.post(formBody)
      					.url("https://www.httpbin.org/post")
      					.build();
      			//準備好請求的Call對象
      			Call call = okHttpClient.newCall(request);
      			try {
      				Response response = call.execute();
      				Log.i("postSync",response.body().string());
      			} catch (IOException e) {
      				e.printStackTrace();
      			}
      		}
      	}).start();
      }
      

      異步POST

      public void postAsync(){//異步請求
      	OkHttpClient okHttpClient=new OkHttpClient();
      	FormBody formBody=new FormBody.Builder() //okhttp3.FormBody extends RequestBody
      			.add("a","1")
      			.add("b","2")
      			.build();
      	Request request=new Request.Builder()
      			.post(formBody)
      			.url("https://www.httpbin.org/post")
      			.build();
      	//準備好請求的Call對象
      	Call call = okHttpClient.newCall(request);
      	call.enqueue(new Callback() {
      		@Override
      		public void onFailure(@NonNull Call call, @NonNull IOException e) {
      
      		}
      
      		@Override
      		public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
      			if(response.isSuccessful()){
      				Log.i("postAsync",response.body().string());
      			}
      		}
      	});
      }
      

      post請求提交多種 MediaType 格式

      text/x-markdown : String、文件

      • post請求與get請求的區別 是在構造Request對象時,需要多構造一個RequestBody對象,用它來攜帶我們要提交的數據,其他都是一樣的。

      示例如下:

      import okhttp3.MediaType;
      import okhttp3.RequestBody;
      
      OkHttpClient httpClient = new OkHttpClient();
      
      MediaType contentType = MediaType.parse("text/x-markdown; charset=utf-8");
      String content = "hello!";
      RequestBody body = RequestBody.create(contentType, content);
      
      Request getRequest = new Request.Builder()
      	.url("https://api.github.com/markdown/raw")
      	.post(body)
      	.build();
      
      Call call = httpClient.newCall(getRequest);
      
      call.enqueue(new Callback() {
      	@Override
      	public void onFailure(Call call, IOException e) {
      	}
      
      	@Override
      	public void onResponse(Call call, Response response) throws IOException {
      		Log.i(TAG, "okHttpPost enqueue: \n onResponse:"+ response.toString() +"\n body:" +response.body().string());
      	}
      });
      
      • 對比get請求,把構建Request時的get()改成post(body),并傳入RequestBody實例。

      RequestBody實例是通過create方法創建,需要指定請求體內容類型、請求體內容。
      這里是傳入了一個指定為markdown格式的文本。

      application/json : json

      • 傳入RequestBodyMediaType 還可以是其他類型,如客戶端要給后臺發送json字符串、發送一張圖片,那么可以定義為:
      • demo1
      // RequestBody:jsonBody,json字符串
      String json = "jsonString";
      RequestBody jsonBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
      
      //RequestBody:fileBody, 上傳文件
      File file = new File(Environment.getExternalStorageDirectory(), "1.png");
      RequestBody fileBody = RequestBody.create(MediaType.parse("image/png"), file);
      
      • demo2
      if(esUrl.endsWith("/_search")){//dsl語法
      	JSONObject jsonObject = JSON.parseObject(sql);
      	jsonStr = JSON.toJSONString(jsonObject);
      } else {//es opendistro 插件的SQL語法
      	json.put("query", sql);
      	jsonStr = JSON.toJSONString(json);
      }
      
      OkHttpClient okHttpClient = SpringContextUtil.getBean(OkHttpClient.class);
      
      String credential = Credentials.basic( username, password);// base64({user}:{password})
      Request request = new Request.Builder()
      		.header("Authorization", credential)
      		.method("POST", RequestBody.create(MediaType.get("application/json"), jsonStr))
      		.url(esUrl)
      		.build();
      
      MediaType
      Call newCall = okHttpClient.newCall(request);//只是配置請求,不會觸發網絡操作 (需確保每個 Call 對象只執行一次,避免重復使用)
      Response response = newCall.execute();//必須調用 execute() 或 enqueue() 才會真正發起請求
      
      if (!response.isSuccessful()) {
      	ObjectMapper mapper = new ObjectMapper();
      	String body = new String(response.body().bytes());
      
      	logger.error("code:{},message:{},body:{}", response.code(), response.message(), body);
      
      	ErrorMessage errorMessage = mapper.readValue(body, ErrorMessage.class);
      	if (null != errorMessage && errorMessage.getError() != null && errorMessage.getError().getType() != null) {
      		throw new RuntimeException(ErrorCodeEnum.QUERY_ES_FAIL.getCode(), ErrorCodeEnum.QUERY_ES_FAIL.getMsg() + ", cause that : " + errorMessage.getError().getType());
      	}
      }
      

      application/x-www-form-urlencoded : 提交表單

      • 構建RequestBody除了上面的方式,還有它的子類FormBody,FormBody用于提交表單鍵值對,這種能滿足平常開發大部分的需求。
      //RequestBody:FormBody,表單鍵值對
      RequestBody formBody = new FormBody.Builder()
      	.add("username", "henry")
      	.add("password", "666")
      	.build();
      
      • FormBody是通過FormBody.Builder構建者模式創建,add鍵值對即可。它的contentType在內部已經指定了。
      public final class FormBody extends RequestBody {
        private static final MediaType CONTENT_TYPE = MediaType.get("application/x-www-form-urlencoded");
        ...
      }
      

      multipart/* : 提交復雜請求體

      • RequestBody另一個子類MultipartBody,用于post請求提交復雜類型的請求體。
      • 復雜請求體可以同時包含多種類型的的請求體數據。

      上面介紹的 post請求 string、文件、表單,只有單一類型
      考慮一種場景–注冊場景,用戶填寫完姓名、電話,同時要上傳頭像圖片,這時注冊接口的請求體就需要 接受 表單鍵值對 以及文件了,那么前面講的的post就無法滿足了。
      那么就要用到MultipartBody了。

      • 源碼
      
      /** An <a >RFC 2387</a>-compliant request body. */
      public final class MultipartBody extends RequestBody {
        /**
         * The "mixed" subtype of "multipart" is intended for use when the body parts are independent and
         * need to be bundled in a particular order. Any "multipart" subtypes that an implementation does
         * not recognize must be treated as being of subtype "mixed".
         */
        public static final MediaType MIXED = MediaType.get("multipart/mixed");
      
        /**
         * The "multipart/alternative" type is syntactically identical to "multipart/mixed", but the
         * semantics are different. In particular, each of the body parts is an "alternative" version of
         * the same information.
         */
        public static final MediaType ALTERNATIVE = MediaType.get("multipart/alternative");
      
        /**
         * This type is syntactically identical to "multipart/mixed", but the semantics are different. In
         * particular, in a digest, the default {@code Content-Type} value for a body part is changed from
         * "text/plain" to "message/rfc822".
         */
        public static final MediaType DIGEST = MediaType.get("multipart/digest");
      
        /**
         * This type is syntactically identical to "multipart/mixed", but the semantics are different. In
         * particular, in a parallel entity, the order of body parts is not significant.
         */
        public static final MediaType PARALLEL = MediaType.get("multipart/parallel");
      
        /**
         * The media-type multipart/form-data follows the rules of all multipart MIME data streams as
         * outlined in RFC 2046. In forms, there are a series of fields to be supplied by the user who
         * fills out the form. Each field has a name. Within a given form, the names are unique.
         */
        public static final MediaType FORM = MediaType.get("multipart/form-data");
      
        ...
      
      • demo
      OkHttpClient httpClient = new OkHttpClient();
      
      // MediaType contentType = MediaType.parse("text/x-markdown; charset=utf-8");
      // String content = "hello!";
      // RequestBody body = RequestBody.create(contentType, content);
      
      //RequestBody:fileBody,上傳文件
      File file = drawableToFile(this, R.mipmap.bigpic, new File("00.jpg"));
      RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpg"), file);
      
      
      //RequestBody:multipartBody, 多類型 (用戶名、密碼、頭像)
      MultipartBody multipartBody = new MultipartBody.Builder()
      		.setType(MultipartBody.FORM)
      		.addFormDataPart("username", "hufeiyang")
      		.addFormDataPart("phone", "123456")
      		.addFormDataPart("touxiang", "00.png", fileBody)
      		.build();
      
      
      Request getRequest = new Request.Builder()
      		.url("http://yun918.cn/study/public/file_upload.php")
      		.post(multipartBody)
      		.build();
      
      Call call = httpClient.newCall(getRequest);
      
      call.enqueue(new Callback() {
      	@Override
      	public void onFailure(Call call, IOException e) {
      
      		Log.i(TAG, "okHttpPost enqueue: \n onFailure:"+ call.request().toString() +"\n body:" +call.request().body().contentType()
      		+"\n IOException:"+e.getMessage());
      	}
      
      	@Override
      	public void onResponse(Call call, Response response) throws IOException {
      		Log.i(TAG, "okHttpPost enqueue: \n onResponse:"+ response.toString() +"\n body:" +response.body().string());
      	}
      });
      

      更多 MedisType

      • MediaType更多類型信息可以查看 RFC 2045

      https://tools.ietf.org/html/rfc2045。

      • 查看各個文件類型所對應的Content-type字符串,可以訪問:

      https://www.runoob.com/http/http-content-type.html

      請求配置項

      • 前置問題
      • 如何全局設置超時時長?
      • 緩存位置、最大緩存大小 呢?
      • 考慮有這樣一個需求,我要監控App通過 OkHttp 發出的 所有 原始請求,以及整個請求所耗費的時間,如何做?

      這些問題,在OkHttp這里很簡單。把OkHttpClient實例的創建,換成以下方式即可:

      OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(15, TimeUnit.SECONDS)
        .readTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .cache(new Cache(getExternalCacheDir(),500 * 1024 * 1024))
        .addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                String url = request.url().toString();
                Log.i(TAG, "intercept: proceed start: url"+ url+ ", at " + System.currentTimeMillis());
                Response response = chain.proceed(request);
                ResponseBody body = response.body();
                Log.i(TAG, "intercept: proceed end: url"+ url+ ", at " + System.currentTimeMillis());
                return response;
            }
        })
        .build();
      

      這里通過OkHttpClient.Builder通過構建者模式設置了連接、讀取、寫入的超時時長,用cache()方法傳入了由緩存目錄、緩存大小構成的Cache實例,這樣就解決了前兩個問題。

      • 使用addInterceptor()方法添加了Interceptor實例,且重寫了intercept方法。
      • Interceptor意為攔截器,intercept()方法會在開始執行請求時調用。
        其中chain.proceed(request)內部是真正請求的過程,是阻塞操作,執行完后會就會得到請求結果ResponseBody
        所以chain.proceed(request)的前后取當前時間,那么就知道整個請求所耗費的時間。上面chain.proceed(request)的前后分別打印的日志和時間,這樣第三個問題也解決了。
      • 具體Interceptor是如何工作,后面介紹。

      另外,通常OkHttpClient實例是全局唯一的,這樣這些基本配置就是統一,且內部維護的連接池也可以有效復用(后面介紹)。

      全局配置的有了,單個請求的也可以有一些單獨的配置。

      Request getRequest = new Request.Builder()
        .url("http://yun918.cn/study/public/file_upload.php")
        .post(multipartBody)
        .addHeader("key","value")
        .cacheControl(CacheControl.FORCE_NETWORK)
        .build();
      
      • 使用addHeader()方法添加了請求頭
      • 使用cacheControl(CacheControl.FORCE_NETWORK)設置此次請求是能使用網絡,不用緩存。(還可以設置只用緩存FORCE_CACHE

      最佳實踐: MyOkHttpClientConfig

      • pom.xml
        <dependency>
          <groupId>com.squareup.okhttp3</groupId>
          <artifactId>okhttp</artifactId>
          <version>4.9.1 or 3.14.9[推薦]</version>
        </dependency>
      
      • MyOkHttpClientConfig
      import lombok.extern.slf4j.Slf4j;
      import okhttp3.ConnectionPool;
      import okhttp3.OkHttpClient;
      import okhttp3.internal.connection.Transmitter;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      import javax.annotation.Nullable;
      import java.util.Map;
      import java.util.concurrent.TimeUnit;
      
      @Configuration
      @Slf4j
      public class OkHttpClientConfig {
      
          //Socket.setSoTimeout(int timeout)
          public final static String OKHTTP_CONNECTTIMEOUT_PARAM = "OKHTTP_CONNECTTIMEOUT";
          public final static String OKHTTP_CONNECTTIMEOUT_DEFAULT = "1000";
      
          public final static String OKHTTP_CALLTIMEOUT_PARAM = "OKHTTP_CALLTIMEOUT";
          public final static String OKHTTP_CALLTIMEOUT_DEFAULT = "3000";
      
          public final static String OKHTTP_READTIMEOUT_PARAM = "OKHTTP_READTIMEOUT";
          public final static String OKHTTP_READTIMEOUT_DEFAULT = "1000";
      
          public final static String OKHTTP_CONNECTIONPOOL_MAXIDLE_PARAM = "OKHTTP_CONNECTIONPOOL_MAXIDLE";
          public final static String OKHTTP_CONNECTIONPOOL_MAXIDLE_DEFAULT = "100";
      
          public final static String OKHTTP_CONNECTIONPOOL_KEPPALIVCEDURATION_PARAM = "OKHTTP_CONNECTIONPOOL_KEPPALIVCEDURATION";
          public final static String OKHTTP_CONNECTIONPOOL_KEPPALIVCEDURATION_DEFAULT = "30000";
      
          @Bean(name = "okHttpClient")
          public OkHttpClient okHttpClient() {
              OkHttpClient okHttpClient = buildOkHttpClient( OkHttpClient.class.getSimpleName() + "SpringBean(okHttpClient)");
              return okHttpClient;
          }
      
          /**
           * 構建 OkHttpClient 連接池
           * @param caller 可選參數,標明調用方
           * @return
           */
          public static OkHttpClient buildOkHttpClient(@Nullable String caller){
      //        MyOkHttpRetryInterceptor.Builder builder = new MyOkHttpRetryInterceptor.Builder()
      //            .retryInterval(500)
      //            .executionCount(3);
      
              Map<String, String> env = System.getenv();
      
              long connectTimeout = Long.valueOf( env.getOrDefault(OKHTTP_CONNECTTIMEOUT_PARAM, OKHTTP_CONNECTTIMEOUT_DEFAULT) );
              long callTimeout = Long.valueOf( env.getOrDefault(OKHTTP_CALLTIMEOUT_PARAM, OKHTTP_CALLTIMEOUT_DEFAULT) );
              long readTimeout = Long.valueOf( env.getOrDefault(OKHTTP_READTIMEOUT_PARAM, OKHTTP_READTIMEOUT_DEFAULT) );
              int maxIdleConnections = Integer.valueOf( env.getOrDefault( OKHTTP_CONNECTIONPOOL_MAXIDLE_PARAM, OKHTTP_CONNECTIONPOOL_MAXIDLE_DEFAULT ) );
              long keepAliveDuration = Long.valueOf( env.getOrDefault( OKHTTP_CONNECTIONPOOL_KEPPALIVCEDURATION_PARAM, OKHTTP_CONNECTIONPOOL_KEPPALIVCEDURATION_DEFAULT ) );
      
              log.info("config({}) | connectTimeout:{}, callTimeout:{}, readTimeout:{}, maxIdleConnections:{}, keepAliveDuration:{}"
                  , caller==null?"":caller
                  , connectTimeout, callTimeout, readTimeout, maxIdleConnections, keepAliveDuration
              );
              return new OkHttpClient().newBuilder()
                  .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                  .callTimeout(callTimeout, TimeUnit.MILLISECONDS)
                  .readTimeout(readTimeout, TimeUnit.MILLISECONDS)
                  .retryOnConnectionFailure(true) //開啟 okhttp 自帶的 重試阻攔器
                  .followRedirects(true)//重定向
                  //.addInterceptor( new MyOkHttpRetryInterceptor(builder) )//本重置攔截器,尚不可靠
                  .connectionPool(new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.MILLISECONDS))
                  .build();
          }
      
          public static OkHttpClient buildOkHttpClient(){
              return buildOkHttpClient(null);
          }
      }
      

      攔截器的使用

      • OkHttp攔截器Interceptors)提供了強大的自定義和修改HTTP請求和響應的能力。
      • 攔截器允許在發送請求前、收到響應后以及其他階段對HTTP流量進行攔截和處理。

      例如:攔截器可以修改請求的URL、請求方法、請求頭部、請求體等。
      這對于添加身份驗證頭、設置緩存控制頭等場景很有用。
      用法如下:

      public void interceptor(){
          OkHttpClient okHttpClient=new OkHttpClient.Builder()//添加攔截器的使用OkHttpClient的內部類Builder
            .addInterceptor(new Interceptor() {//使用攔截器可以對所有的請求進行統一處理,而不必每個request單獨去處理
                @NonNull
                @Override
                public Response intercept(@NonNull Chain chain) throws IOException {
                    //前置處理,以proceed方法為分割線:提交請求前
                    Request request = chain.request().newBuilder()
                            .addHeader("id", "first request")
                            .build();
                    Response response = chain.proceed(request);
                    //后置處理:收到響應后
                    return response;
                }
            })
            .addNetworkInterceptor(new Interceptor() {//這個在Interceptor的后面執行,無論添加順序如何
                @NonNull
                @Override
                public Response intercept(@NonNull Chain chain) throws IOException {
                    Log.i("id",chain.request().header("id"));
                    return chain.proceed(chain.request());
                }
            })
            .cache(new Cache(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()+"/cache"),1024*1024))//添加緩存
            .build();
      
          Request request=new Request.Builder()
            .url("https://www.httpbin.org/get?a=1&b=2")
            .build();
      
          //準備好請求的Call對象
          Call call = okHttpClient.newCall(request);
          //異步請求
          call.enqueue(new Callback() {
              @Override
              public void onFailure(@NonNull Call call, @NonNull IOException e) {
      
              }
      
              @Override
              public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                  if(response.isSuccessful()){
                      Log.i("interceptor",response.body().string());
                  }
              }
          });
      }
      

      Cookie的使用

      • 大家應該有這樣的經歷,就是有些網站的好多功能都需要用戶登錄之后才能訪問,而這個功能可以用cookie實現:
      • 首先,在客戶端登錄之后,服務器給客戶端發送一個cookie,由客戶端保存;
      • 然后,客戶端在訪問需要登錄之后才能訪問的功能時,只要攜帶這個cookie,服務器就可以識別該用戶是否登錄。

      用法如下

      public void cookie(){
          Map<String,List<Cookie>> cookies = new HashMap<>();
      
          new Thread(new Runnable() {
              @Override
              public void run() {
                  OkHttpClient okHttpClient = new OkHttpClient.Builder()
                      .cookieJar(new CookieJar() {
                          @Override
                          public void saveFromResponse(@NonNull HttpUrl httpUrl, @NonNull List<Cookie> list) {//保存服務器發送過來的cookie
                              cookies.put("cookies",list);
                          }
      
                          @NonNull
                          @Override
                          public List<Cookie> loadForRequest(@NonNull HttpUrl httpUrl) {//請求的時候攜帶cookie
                              if(httpUrl.equals("www.wanandroid.com")){
                                  return cookies.get("cookies");
                              }
                              return new ArrayList<>();
                          }
                      })
                      .build();
      
                  FormBody formBody = new FormBody.Builder()
                          .add("username","ibiubiubiu")
                          .add("password","Lhh823924.")
                          .build();
      
                  Request request = new Request.Builder()  //模擬登錄
                          .url("https://wanandroid.com/user/lg")
                          .post(formBody)
                          .build();
      
                  //準備好請求的Call對象
                  Call call = okHttpClient.newCall(request);
                  try {
                      Response response = call.execute();
                      Log.i("login",response.body().string());
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
      
                  //請求收藏頁面,必須登錄之后才能訪問到
                  request=new Request.Builder()
                          .url("https://wanandroid.com/lg/collect")
                          .build();
      
                  //準備好請求的Call對象
                  call = okHttpClient.newCall(request);
                  try {
                      Response response = call.execute();
                      Log.i("collect",response.body().string());
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          })
          .start();
      }
      

      連接池 : OkHttp ConnectionPool

      基礎概念

      • OkHttp ConnectionPool 是 OkHttp 庫中的一個關鍵組件,用于管理 HTTP 連接的復用。

      • OkHttp ConnectionPool 是一個連接池,用于存儲和管理 HTTP 連接。

      它允許在多個請求之間復用連接,從而提高網絡請求的效率
      連接池中的連接可以被多個線程共享,并且會根據配置的策略(如最大空閑連接數、連接超時時間等)來管理連接的生命周期。

      優勢

      • 提高性能:通過復用連接,減少了每次請求時的握手和連接建立時間。
      • 減少資源消耗:避免頻繁地創建和銷毀連接,節省了系統資源。
      • 支持并發:允許多個線程同時使用同一個連接,提高了并發處理能力。

      參數項

      OkHttp ConnectionPool 主要有以下幾種配置參數:

      • maxIdleConnections:池中最大空閑連接數。
      • keepAliveDuration:連接保持活躍的時間。
      • timeUnit:keepAliveDuration 的時間單位。

      應用場景

      • Web 服務器和客戶端之間的通信:在高并發場景下,使用連接池可以顯著提高響應速度。
      • 移動應用網絡請求:在移動應用中,頻繁的網絡請求可以通過連接池優化性能。
      • 微服務架構中的服務間調用:在微服務架構中,服務之間的調用可以通過連接池提高效率。

      常用方法

      //連接數 : Returns total number of connections in the pool.
      int : okHttpClient.connectionPool().connectionCount();
      
      //空閑連接數 : Returns the number of idle connections in the pool.
      int : okHttpClient.connectionPool().idleConnectionCount()
      
      //關閉所有連接 : Close and remove all idle connections in the pool.
      void : okHttpClient.connectionPool().evictAll();
      
      
      • okhttp3.ConnectionPool的 底層實現類: okhttp3.internal.connection.RealConnectionPool

      借助連接池可解決的常見問題

      Q: 連接池中的連接超時 / 配置連接池

      • 原因:可能是由于網絡不穩定或服務器響應慢導致的。

      • 解決方法

      import okhttp3.ConnectionPool;
      
      ConnectionPool pool = new ConnectionPool(5, 5, TimeUnit.MINUTES);
      OkHttpClient client = new OkHttpClient.Builder()
          .connectionPool(pool)
          .readTimeout(30, TimeUnit.SECONDS)
          .writeTimeout(30, TimeUnit.SECONDS)
          .connectTimeout(30, TimeUnit.SECONDS)
          .build();
      

      Q: 連接池的默認配置

      • 連接池默認配置
      • 每個地址的空閑連接數為 5個,每個空閑連接的存活時間為 5分鐘
      package okhttp3;
      
      public ConnectionPool() {
          this(5, 5, TimeUnit.MINUTES);
      }
      
      public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
          this.maxIdleConnections = maxIdleConnections;
          this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
      
          if (keepAliveDuration <= 0) {
              throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
          }
      }
      

      Q: 過期連接的處理策略?

      • 連接池每次添加一個新的連接時,都會先清理當前連接池中過期的連接,通過清理線程池executor 執行清理任務cleanupRunnable

      Q: 連接池中的連接數過多

      • 原因:可能是由于客戶端請求過于頻繁,導致連接池中的連接數超過了設定的最大值。

      • 解決方案:

      ConnectionPool pool = new ConnectionPool(100, 5, TimeUnit.MINUTES);
      OkHttpClient client = new OkHttpClient.Builder()
          .connectionPool(pool)
          .build();
      

      Q: 連接池中的連接無法釋放

      • 原因:可能是由于某些請求未正確關閉連接導致的。

      • 解決方案:

      確保每次請求后都正確關閉響應體:

      Response response = client.newCall(request).execute();
      try {
          // 處理響應
      } finally {
          response.close();
      }
      
      • 綜合案例
      import okhttp3.ConnectionPool;
      import okhttp3.OkHttpClient;
      import java.util.concurrent.TimeUnit;
      
      public class OkHttpExample {
          public static void main(String[] args) {
              ConnectionPool pool = new ConnectionPool(100, 5, TimeUnit.MINUTES);
              OkHttpClient client = new OkHttpClient.Builder()
                  .connectionPool(pool)
                  .readTimeout(30, TimeUnit.SECONDS)
                  .writeTimeout(30, TimeUnit.SECONDS)
                  .connectTimeout(30, TimeUnit.SECONDS)
                  .build();
      
              // 使用 client 進行網絡請求
          }
      }
      

      通過合理配置和使用 OkHttp ConnectionPool,可以有效提升網絡請求的性能和穩定性。

      參考文獻

      綜合應用

      案例1:封裝OkHttp單例模式 : OkHttpUtils(自定義)

      public class OkHttpUtils {
          /**
           * 單例模式
           */
          private static OkHttpUtils okHttpUtils = null;
       
          private OkHttpUtils() {
          }
       
          public static OkHttpUtils getInstance() {
              //雙層判斷,同步鎖
              if (okHttpUtils == null) {
                  synchronized (OkHttpUtils.class) {
                      if(okHttpUtils == null){
                          okHttpUtils = new OkHttpUtils();
                      }
                  }
              }
              return okHttpUtils;
          }
       
          /**
           * 單例模式
           * 封裝OkhHttp
           * synchronized同步方法
           */
          private static OkHttpClient okHttpClient = null;
       
          private static synchronized OkHttpClient getOkHttpClient() {
              if (okHttpClient == null) {
                  //攔截器
                  HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                      @Override
                      public void log(String message) {
                          //攔截日志消息
                          Log.i("henry", "log: " + message);
                      }
                  });
                  //設置日志攔截器模式
                  interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
       
                  okHttpClient = new OkHttpClient.Builder()
                      //日志攔截器
                      .addInterceptor(interceptor)
                      //應用攔截器
                      .addInterceptor(new Interceptor() {
                          @Override
                          public Response intercept(Chain chain) throws IOException {
                              Request request = chain.request()
                                      .newBuilder()
                                      .addHeader("source", "android")
                                      .build();
       
                              return chain.proceed(request);
                          }
                      })
                      .build();
              }
              return okHttpClient;
          }
       
          /**
           * doGet
           */
          public void doGet(String url, Callback callback) {
              //創建okhttp
              OkHttpClient okHttpClient = getOkHttpClient();
              Request request = new Request.Builder()
                      .url(url)
                      .build();
              okHttpClient.newCall(request).enqueue(callback);
          }
       
          /**
           * doPost
           */
          public void doPost(String url, Map<String, String> params, Callback callback) {
              OkHttpClient okHttpClient = getOkHttpClient();
              //請求體
              FormBody.Builder formBody = new FormBody.Builder();
              for (String key : params.keySet()) {
                  //遍歷map集合
                  formBody.add(key, params.get(key));
              }
              Request request = new Request.Builder()
                      .url(url)
                      .post(formBody.build())
                      .build();
              okHttpClient.newCall(request).enqueue(callback);
          }
      }
      

      實現了單例模式來確保 OkHttpUtilsOkHttpClient 實例的唯一性

      案例2:配置https的自簽證書和信任所有證書

      HTTPS / SSL 證書

      • HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,要比http協議安全。

      一般支持https的網站,都是CA(Certificate Authority)機構頒發的證書,但是一般該機構頒發的證書需要提供費用且有使用時間的限制,到期需要續費。
      否則,默認該鏈接是不信任的,通過okHttp無法直接訪問。

      但是我們可以使用自簽的方式,通過JDK自帶的keytool.exe 生成一個自己的證書,然后使用該證書內容。
      雖然也是會出現提示“不安全”,但是我們可以通過okhttp訪問鏈接。

      使用自簽證書

      • 將證書文件放置在assets目錄(也可以放置在其他目錄下,只要能正確讀取到該文件),在創建OkhttpClient對象時sslSocketFactory()將該證書信息添加。
      private SSLContext getSLLContext() {
              SSLContext sslContext = null;
              try {
                  CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                  InputStream certificate = mContext.getAssets().open("gdroot-g2.crt");
                  KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                  keyStore.load(null);
                  String certificateAlias = Integer.toString(0);
                  keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
                  sslContext = SSLContext.getInstance("TLS");
                  final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                  trustManagerFactory.init(keyStore);
                  sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
              } catch (CertificateException e) {
                  e.printStackTrace();
              } catch (KeyStoreException e) {
                  e.printStackTrace();
              } catch (NoSuchAlgorithmException e) {
                  e.printStackTrace();
              } catch (IOException e) {
                  e.printStackTrace();
              } catch (KeyManagementException e) {
                  e.printStackTrace();
              }
              return sslContext;
          }
      

      信任所有證書

      • 通過添加證書的形式,可以實現客戶端訪問Https服務端的功能,但是如果服務端更換證書內容,那么客戶端需要相應的更換https證書,否則無法正常交互獲取不到數據,我們可以通過自定義X509TrustManager的形式實現來規避所有的證書檢測,實現信任所有證書的目的。
       private OkHttpClient getHttpsClient() {
              OkHttpClient.Builder okhttpClient = new OkHttpClient().newBuilder();
              //信任所有服務器地址
              okhttpClient.hostnameVerifier(new HostnameVerifier() {
                  @Override
                  public boolean verify(String s, SSLSession sslSession) {
                      //設置為true
                      return true;
                  }
              });
              //創建管理器
              TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
                  @Override
                  public void checkClientTrusted(
                          java.security.cert.X509Certificate[] x509Certificates,
                          String s) throws java.security.cert.CertificateException {
                  }
      
                  @Override
                  public void checkServerTrusted(
                          java.security.cert.X509Certificate[] x509Certificates,
                          String s) throws java.security.cert.CertificateException {
                  }
      
                  @Override
                  public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                      return new java.security.cert.X509Certificate[] {};
                  }
              } };
              try {
                  SSLContext sslContext = SSLContext.getInstance("TLS");
                  sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
      
                  //為OkHttpClient設置sslSocketFactory
                  okhttpClient.sslSocketFactory(sslContext.getSocketFactory());
      
              } catch (Exception e) {
                  e.printStackTrace();
              }
      
              return okhttpClient.build();
          }
      
      • 創建X509TrustManager對象,并實現其中的方法。由于X509TrustManager通用證書格式,只需要拿到該格式就行。最后init該安全協議,將其放入okhttpsslSocketFactory中。
      • 由于Retrofit只是對Okhttp網絡接口的封裝。因此實際使用中,該方法同樣適用于Retrofit中。

      原理與架構

      okhttp 請求過程

      FAQ for okhttp

      面試可能會問到的問題:

      • 簡單說一下okhttp
      • okhttp的核心類有哪些?
      • okhttp對于網絡請求做了哪些優化,如何實現的?
      • okhttp架構中用到了哪些設計模式?
      • okhttp攔截器的執行順序

      Q: OkHttp3 超時設置(callTimeout/connectTimeout/readTimeout)?

      okhttp:3.14.9

      場景導入: Socket closed , SocketTimeoutException

      假設應用程序使用okhttp3框架時,報如下錯誤,考慮下如何解決?

      [TID: xxx.100.xxxx01] [ds-service] [system] [2025/03/13 09:56:44.512] [DEBUG] [http-nio-9527-exec-6] [AbstractQuery] logDatabaseQuerySQL:113 ElasticSearchQuery | database final query sql: select xxx from xxx #other-info# [pageSql]
      [TID: xxx.100.xxxx01] [ds-service] [system] [2025/03/13 09:56:45.515] [ERROR] [http-nio-9527-exec-6] [ElasticSearchConnector] post:387 Fail to execute for send post request cause that the exception!url:http://127.0.0.1:9200/_opendistro/_sql?format=json, requestJson:`select xxx from xxx`,exception:
      java.net.SocketTimeoutException: timeout
      	at okio.Okio$4.newTimeoutException(Okio.java:232) ~[okio-1.17.2.jar!/:?]
      	at okio.AsyncTimeout.exit(AsyncTimeout.java:286) ~[okio-1.17.2.jar!/:?]
      	at okio.AsyncTimeout$2.read(AsyncTimeout.java:241) ~[okio-1.17.2.jar!/:?]
      	at okio.RealBufferedSource.indexOf(RealBufferedSource.java:358) ~[okio-1.17.2.jar!/:?]
      	at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:230) ~[okio-1.17.2.jar!/:?]
      	at okhttp3.internal.http1.Http1ExchangeCodec.readHeaderLine(Http1ExchangeCodec.java:242) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.java:213) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.connection.Exchange.readResponseHeaders(Exchange.java:115) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.java:94) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:43) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:94) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:88) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.RealCall.getResponseWithInterceptorChain$original$BnZzvgYR(RealCall.java:229) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.RealCall.getResponseWithInterceptorChain$original$BnZzvgYR$accessor$VWw5Xe8t(RealCall.java) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.RealCall$auxiliary$aiLFzmy0.call(Unknown Source) ~[okhttp-3.14.9.jar!/:?]
      	at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:86) ~[skywalking-agent.jar:8.9.0]
      	at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.RealCall.execute$original$BnZzvgYR(RealCall.java:81) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.RealCall.execute$original$BnZzvgYR$accessor$VWw5Xe8t(RealCall.java) ~[okhttp-3.14.9.jar!/:?]
      	at okhttp3.RealCall$auxiliary$JZ7wpldV.call(Unknown Source) ~[okhttp-3.14.9.jar!/:?]
      	at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:86) ~[skywalking-agent.jar:8.9.0]
      	at okhttp3.RealCall.execute(RealCall.java) ~[okhttp-3.14.9.jar!/:?]
      	at com.xxx.common.connector.elasticsearch.ElasticSearchConnector.post(ElasticSearchConnector.java:385) [ds-service-common-1.1.24-SNAPSHOT.jar!/:?]
      	at com.xxx.common.connector.elasticsearch.ElasticSearchConnector.post(ElasticSearchConnector.java:328) [ds-service-common-1.1.24-SNAPSHOT.jar!/:?]
      	at com.xxx.common.query.ElasticSearchQuery.post(ElasticSearchQuery.java:116) [ds-service-common-1.1.24-SNAPSHOT.jar!/:?]
      	at com.xxx.common.query.ElasticSearchQuery.autoPagingQuery(ElasticSearchQuery.java:101) [ds-service-common-1.1.24-SNAPSHOT.jar!/:?]
      	at com.xxx.common.query.QueryFactory.query(QueryFactory.java:88) [ds-service-common-1.1.24-SNAPSHOT.jar!/:?]
      	at com.xxx.biz.ds.service.impl.CommonSearchBizServiceImpl.queryDatasourceData(CommonSearchBizServiceImpl.java:398) [classes!/:?]
      	at com.xxx.biz.ds.service.impl.CommonSearchBizServiceImpl.executeQuery(CommonSearchBizServiceImpl.java:160) [classes!/:?]
      	at com.xxx.biz.ds.service.impl.CommonSearchBizServiceImpl.executeQuery(CommonSearchBizServiceImpl.java:337) [classes!/:?]
      	at com.xxx.biz.ds.controller.v2.DataSearchController.datasetQuery$original$Ezz0kp0w(DataSearchController.java:158) [classes!/:?]
      	at com.xxx.biz.ds.controller.v2.DataSearchController.datasetQuery$original$Ezz0kp0w$accessor$8fPZVSws(DataSearchController.java) [classes!/:?]
      	at com.xxx.biz.ds.controller.v2.DataSearchController$auxiliary$XARzqMqE.call(Unknown Source) [classes!/:?]
      	at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:86) [skywalking-agent.jar:8.9.0]
      	at com.xxx.biz.ds.controller.v2.DataSearchController.datasetQuery(DataSearchController.java) [classes!/:?]
      	at com.xxx.biz.ds.controller.v2.DataSearchController$$FastClassBySpringCGLIB$$6bfcdaf9.invoke(<generated>) [classes!/:?]
      	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) [spring-core-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) [spring-aop-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) [spring-aop-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) [spring-aop-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:119) [spring-context-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) [spring-aop-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) [spring-aop-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) [spring-aop-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at com.xxx.biz.ds.controller.v2.DataSearchController$$EnhancerBySpringCGLIB$$957c5021.datasetQuery(<generated>) [classes!/:?]
      	at sun.reflect.GeneratedMethodAccessor257.invoke(Unknown Source) ~[?:?]
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_422]
      	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_422]
      	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) [spring-web-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) [spring-web-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) [spring-webmvc-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878) [spring-webmvc-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792) [spring-webmvc-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) [spring-webmvc-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) [spring-webmvc-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) [spring-webmvc-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) [spring-webmvc-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) [spring-webmvc-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at javax.servlet.http.HttpServlet.service(HttpServlet.java:665) [javax.servlet-api-4.0.1.jar!/:4.0.1]
      	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) [spring-webmvc-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at javax.servlet.http.HttpServlet.service(HttpServlet.java:750) [javax.servlet-api-4.0.1.jar!/:4.0.1]
      	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) [tomcat-embed-websocket-9.0.46.jar!/:?]
      	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.skywalking.apm.agent.core.context.TraceMdcWebFilter.doFilter(TraceMdcWebFilter.java:58) [classes!/:8.9.0]
      	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar!/:?]
      	at com.xxx.biz.common.filter.AccessPathWebFilter.doFilter(AccessPathWebFilter.java:92) [classes!/:?]
      	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) [spring-web-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) [spring-web-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:97) [spring-boot-actuator-2.3.12.RELEASE.jar!/:2.3.12.RELEASE]
      	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) [spring-web-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.15.RELEASE.jar!/:5.2.15.RELEASE]
      	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.StandardHostValve.invoke$original$q8sTk9iR(StandardHostValve.java:143) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.StandardHostValve.invoke$original$q8sTk9iR$accessor$N646g7Eq(StandardHostValve.java) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.StandardHostValve$auxiliary$MLBge40B.call(Unknown Source) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:86) [skywalking-agent.jar:8.9.0]
      	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:764) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) [tomcat-embed-core-9.0.46.jar!/:?]
      	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.46.jar!/:?]
      	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_422]
      		at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_422]
      		at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.46.jar!/:?]
      		at java.lang.Thread.run(Thread.java:750) [?:1.8.0_422]
      	Caused by: java.net.SocketException: Socket closed
      		at java.net.SocketInputStream.read(SocketInputStream.java:204) ~[?:1.8.0_422]
      		at java.net.SocketInputStream.read(SocketInputStream.java:141) ~[?:1.8.0_422]
      		at okio.Okio$2.read(Okio.java:140) ~[okio-1.17.2.jar!/:?]
      		at okio.AsyncTimeout$2.read(AsyncTimeout.java:237) ~[okio-1.17.2.jar!/:?]
      		... 115 more
      

      OkHttpClient 配置解釋

      OkHttpClient httpClient = new OkHttpClient.Builder()
      	.callTimeout(CALL_TIMEOUT, TimeUnit.MILLISECONDS)
      	.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) //連接超時
      	.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) //讀取超時
      	.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS) //寫超時
          //.pingInterval(1000, TimeUnit.MILLISECONDS) //此配置項,會定時的向服務器發送一個消息來保持長連接; 只有 http2 和 webSocket 中有使用
      
      	.protocols( protocols ) //  Protocol.HTTP_1_1 / ...
      	.retryOnConnectionFailure(true)//連接失敗時是否重試
      	.followRedirects(true)//重定向
      	.connectionPool(new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.MILLISECONDS))
      	//.addNetworkInterceptor(new EncryptInterceptor())
      	//.addInterceptor( new MyOkHttpRetryInterceptor(builder) )//本重置攔截器,尚不可靠
      	//.addInterceptor(new CommonHeaderInterceptor())
      	//.addInterceptor(new CacheInterceptor())
      	//.addInterceptor(new HttpLoggerInterceptor())
      	.build();
      
      • 超時參數
      • connectTimeout

      指的是建立連接所用的時間,適用于網絡狀況正常的情況下,兩端連接所用的時間
      通過跟源碼發現這個值用在了 socket.connect(address, connectTimeout);

      即:最終設置給了socket (確切的說應該是rawSocket)

      • readTimeout 最終設置給了rawSocket 以及 在socket基礎上創建的BufferedSource
      • writeTimeout 最終設置給了在socket基礎上創建的BufferedSink
      • okhttp => http / socket => tcp

      okhttp底層基于socket, 故 Timeout 自然也是設置給Socket 的 connect / read / write
      socket是對于傳輸層的抽象。我們這里討論的是http, 所以對socket設置各種timeout 其實也就是對于TCP的timeout進行配置

      TCP協議(握手/揮手/發包/丟包重傳/滑動窗口/擁塞控制等細節)以及socket屬于前置知識

      源碼探究

      • okhttp 采用了責任鏈的設計模式
      • 用一條抽象的 Chain 將一堆 Interceptor 串起來
      • 從發出 request 到接收 response 的路徑類似于 node.js 中 koa2 的“洋蔥模型”(圖1),而 okhttp 的 Interceptor 作用就相當于koa2中的 middleware

      圖1: koa洋蔥模型

      洋蔥”的每一層都是一個Interceptor,每一層都專注于自己的事情(單一職責),比如日志、mock api、弱網模擬、統一header、APP層緩存、通訊加密等,功能拆分,互不影響,從框架層面來講也是對AOP思想的具體實踐。(AOP可不僅僅是傳統意義上的字節碼插樁)

      圖2: okhttp中的洋蔥模型

      • okhttp本身已提供了幾個Interceptor的默認實現
      • CacheInterceptor : 對于 http1.1 緩存機制的具體實現(cache-controll等)
      • ConnectInterceptor : 專門負責創建/復用TCP連接, 里面的 ConnectionPool 就是對 http1.1 中 keep-alive(TCP連接復用)和 pipline機制(用多條TCP連接實現并發請求)的具體實現,而超時相關的設置也是從這里切入。

      okhttp3.internal.connection.Transmitter

      okhttp3.RealCall#newRealCall + okhttp3.internal.connection.Transmitter : 配置 callTimeout

      • okhttp3.RealCall
      package okhttp3;
      
      final class RealCall implements Call {  
        ...
      
        private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
          this.client = client;
          this.originalRequest = originalRequest;
          this.forWebSocket = forWebSocket;
        }
      
        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.transmitter = new Transmitter(client, call);
          return call;
        }
      
        ...
      
      }
      
      • okhttp3.internal.connection.Transmitter
      package okhttp3.internal.connection;
      
      public final class Transmitter {
        private final OkHttpClient client;
        private final RealConnectionPool connectionPool;
        private final Call call;
        private final EventListener eventListener;
        private final AsyncTimeout timeout = new AsyncTimeout() {
          @Override protected void timedOut() {
            cancel();//超時將 cancel 請求
          }
        };
      
        // ...
      
        public Transmitter(OkHttpClient client, Call call) {
          this.client = client;
          this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool());
          this.call = call;
          this.eventListener = client.eventListenerFactory().create(call);
          this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);//callTimeout
        }
      
        public void timeoutEnter() {
          timeout.enter();
        }
      
        private @Nullable IOException timeoutExit(@Nullable IOException cause) {
          if (timeoutEarlyExit) return cause;
          if (!timeout.exit()) return cause;
      
          InterruptedIOException e = new InterruptedIOException("timeout");//留意此行,InterruptedIOException
          if (cause != null) e.initCause(cause);
      
          return e;
        }
      
      
        public void cancel() {//取消請求 (如:請求超時時)
          Exchange exchangeToCancel;
          RealConnection connectionToCancel;
          synchronized (connectionPool) {
            canceled = true;
            exchangeToCancel = exchange;
            connectionToCancel = exchangeFinder != null && exchangeFinder.connectingConnection() != null
                ? exchangeFinder.connectingConnection()
                : connection;
          }
          if (exchangeToCancel != null) {
            exchangeToCancel.cancel();
          } else if (connectionToCancel != null) {
            connectionToCancel.cancel();
          }
        }
      

      Realcall : 內置 ConnectInterceptor / CallServerInterceptor 等阻攔器

      okhttp3.RealCall.AsyncCall#execute
      • okhttp3.RealCall : 每次調用
      • okhttp3.RealCall.AsyncCall#execute
          @Override protected void execute() {
            boolean signalledCallback = false;
            transmitter.timeoutEnter();
            try {
              Response response = getResponseWithInterceptorChain();//關鍵行 : 通過攔截器鏈式調用 的方式 獲取 response
              signalledCallback = true;
              responseCallback.onResponse(RealCall.this, response);
            } catch (IOException e) {
              if (signalledCallback) {
                // Do not signal the callback twice!
                Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
              } else {
                responseCallback.onFailure(RealCall.this, e);
              }
            } catch (Throwable t) {
              cancel();
              if (!signalledCallback) {
                IOException canceledException = new IOException("canceled due to " + t);
                canceledException.addSuppressed(t);
                responseCallback.onFailure(RealCall.this, canceledException);
              }
              throw t;
            } finally {
              client.dispatcher().finished(this);
            }
          }
      
      okhttp3.RealCall#getResponseWithInterceptorChain
        Response getResponseWithInterceptorChain() throws IOException {
          // Build a full stack of interceptors.
          List<Interceptor> interceptors = new ArrayList<>();
          interceptors.addAll(client.interceptors());
          interceptors.add(new RetryAndFollowUpInterceptor(client));
          interceptors.add(new BridgeInterceptor(client.cookieJar()));
          interceptors.add(new CacheInterceptor(client.internalCache()));
          interceptors.add(new ConnectInterceptor(client));//內置了對 ConnectInterceptor 的引用
          if (!forWebSocket) {
            interceptors.addAll(client.networkInterceptors());
          }
          interceptors.add(new CallServerInterceptor(forWebSocket));
      
          Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
              originalRequest, this, client.connectTimeoutMillis(),
              client.readTimeoutMillis(), client.writeTimeoutMillis());
      
          boolean calledNoMoreExchanges = false;
          try {
            Response response = chain.proceed(originalRequest);
            if (transmitter.isCanceled()) {
              closeQuietly(response);
              throw new IOException("Canceled");
            }
            return response;
          } catch (IOException e) {
            calledNoMoreExchanges = true;
            throw transmitter.noMoreExchanges(e);
          } finally {
            if (!calledNoMoreExchanges) {
              transmitter.noMoreExchanges(null);
            }
          }
        }
      

      ConnectInterceptor

      • okhttp3.internal.connection.ConnectInterceptor
      /** Opens a connection to the target server and proceeds to the next interceptor. */
      public final class ConnectInterceptor implements Interceptor {
        public final OkHttpClient client;
      
        public ConnectInterceptor(OkHttpClient client) {
          this.client = client;
        }
      
        @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");
          // 入口在 newStream 方法
          HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
      
          RealConnection connection = streamAllocation.connection();
          return realChain.proceed(request, streamAllocation, httpCodec, connection);
        }
      }
      

      上面的 StreamAllocation#newStream 方法就做了2件事

      public HttpCodec newStream(OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
          // 這里的chain就是RealInterceptorChain,它里面的各種timeout值都是通過我們創建HttpClient時原封不動賦給它的,下面只是它的一些get方法;
          int connectTimeout = chain.connectTimeoutMillis();
          int readTimeout = chain.readTimeoutMillis();
          int writeTimeout = chain.writeTimeoutMillis();
          int pingIntervalMillis = client.pingIntervalMillis();
          boolean connectionRetryEnabled = client.retryOnConnectionFailure();
          //簡化后的代碼
          ...
            // 3.1  findHealthyConnection 會調用 findConnection
            RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
                writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
            // 3.2
            HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
            return resultCodec;
        }
      

      okio.AsyncTimeout

      package okio;
      
      public class AsyncTimeout extends Timeout {
          ...
      }
      

      callTimeout,readTimeout,writeTimeout和okio的AsyncTimeout有著密不可分的關系,其內部維護了一個靜態內部類Watchdog: 單獨開一個線程死循環判斷是否超時

      • okio.AsyncTimeout.Watchdog
        private static final class Watchdog extends Thread {
          Watchdog() {
            super("Okio Watchdog");
            setDaemon(true);
          }
      
          public void run() {
            while (true) {
              try {
                AsyncTimeout timedOut;
                synchronized (AsyncTimeout.class) {
                  timedOut = awaitTimeout();
      
                  // Didn't find a node to interrupt. Try again.
                  if (timedOut == null) continue;
      
                  // The queue is completely empty. Let this thread exit and let another watchdog thread
                  // get created on the next call to scheduleTimeout().
                  if (timedOut == head) {
                    head = null;
                    return;
                  }
                }
      
                // Close the timed out node.
                timedOut.timedOut();
              } catch (InterruptedException ignored) {
              }
            }
          }
        }
      

      參考文獻

      Y 推薦資源

      X 參考文獻

      posted @ 2025-03-03 21:45  千千寰宇  閱讀(213)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 可以直接看的无码av| 免费人妻无码不卡中文18禁| 中文字幕v亚洲日本在线电影| 国产精品综合在线免费看| 人人人澡人人肉久久精品| 激情久久综合精品久久人妻| 亚洲小说乱欧美另类| 欧美成人猛片aaaaaaa| 国产成人亚洲无码淙合青草| 免费A级毛片中文字幕| 国产成人午夜福利在线播放| 色一情一乱一区二区三区码 | 国产熟睡乱子伦视频在线播放| 亚洲av永久无码精品天堂久久| 精品久久久无码人妻中文字幕| 亚洲成人高清av在线| av新版天堂在线观看| 日本边吃奶边摸边做在线视频 | 四虎永久地址www成人| 免费人成网站视频在线观看 | 成人午夜视频一区二区无码| 午夜综合网| 国产女人喷潮视频免费| 日本一二三区视频在线| 综合人妻久久一区二区精品| 日本一区不卡高清更新二区 | 91福利视频一区二区| 免费无码中文字幕A级毛片| 四虎在线成人免费观看| 精品视频福利| 久久天天躁夜夜躁狠狠| 老色99久久九九爱精品| 特级av毛片免费观看| 日韩午夜福利片段在线观看 | 日本一区二区a√成人片| 东京热无码av男人的天堂| 综合色综合色综合色综合| 国产精品美女久久久| 吴江市| 人妻少妇偷人一区二区| 麻豆tv入口在线看|