Okhttp解析(一)請(qǐng)求的分發(fā),攔截

Okhttp特性

Okhttp是一個(gè)高效的,請(qǐng)求速度更快,更節(jié)省流量的http庫(kù)。擁有以下特性。

  1. 支持SPDY和http2,對(duì)同一服務(wù)器的所有請(qǐng)求共享同一個(gè)socket。
  2. 擁有自動(dòng)維護(hù)的socket連接池,減少握手次數(shù)。
  3. socket自動(dòng)選擇最好路線(xiàn),并支持自動(dòng)重連。
  4. 擁有隊(duì)列線(xiàn)程池,輕松寫(xiě)并發(fā)。
  5. 擁有Interceptors輕松處理請(qǐng)求與響應(yīng)(比如透明GZIP壓縮,LOGGING)。
  6. 無(wú)縫的支持GZIP來(lái)減少數(shù)據(jù)流量。
  7. 支持基于Headers的緩存策略,緩存響應(yīng)數(shù)據(jù)來(lái)減少重復(fù)的網(wǎng)絡(luò)請(qǐng)求。
  8. 支持服務(wù)器多IP重連,支持從常用的連接問(wèn)題中自動(dòng)恢復(fù),還處理了代理服務(wù)器問(wèn)題和SSL握手失敗問(wèn)題。
注:SPDY是什么?
SPDY(讀作“SPeeDY”)是Google開(kāi)發(fā)的基于TCP的傳輸層協(xié)議,用以最小化網(wǎng)絡(luò)延遲,提升網(wǎng)絡(luò)速度,優(yōu)化用戶(hù)的網(wǎng)絡(luò)使用體驗(yàn)。
SPDY并不是一種用于替代HTTP的協(xié)議,而是對(duì)HTTP協(xié)議的增強(qiáng)。新協(xié)議的功能包括數(shù)據(jù)流的多路復(fù)用、請(qǐng)求優(yōu)先級(jí)以及HTTP報(bào)頭壓縮。
谷歌表示,引入SPDY協(xié)議后,在實(shí)驗(yàn)室測(cè)試中頁(yè)面加載速度比原先快64%。

OkHttpClient分析版本

Okhttp3(3.2.0版本)

OkHttpClient的簡(jiǎn)單調(diào)用

Get請(qǐng)求

public void doGet(String url){
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url(url).build();
    Response response = client.newCall(request).execute();
    Log.i("輸出:" + response.body().string());
}

Post請(qǐng)求

public void doPost(String url){
    MediaType json = MediaType.parse("application/json; charset=utf-8")
    RequestBody body = RequestBody.create(JSON, json);
    
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url(url).post(body).build();
    Response response = client.newCall(request).execute();
    Log.i("輸出:" + response.body().string());
}

OkHttpClient介紹

OkHttpClient顧名思義是一個(gè)http請(qǐng)求的客戶(hù)端,實(shí)現(xiàn)了Call.Factory接口,提供newCall方法用于創(chuàng)建一個(gè)請(qǐng)求調(diào)用Call。

@Override public Call newCall(Request request) {
    return new RealCall(this, request);
}

OkHttpClient包含很多模塊,包括Dispatcher(請(qǐng)求分發(fā)器),ProxySelector(代理服務(wù)器選擇),InternalCache(內(nèi)部緩存)等其他的模塊,由它對(duì)外提供這些模塊的訪問(wèn),是典型的外觀模式,同時(shí)OkHttpClient設(shè)計(jì)為建造者模式,提供Builder方便為不同模塊進(jìn)行配置。

Request,Response,Call,RealCall,AsynCall介紹

  • Request作為請(qǐng)求信息的封裝,內(nèi)部包含了請(qǐng)求url,請(qǐng)求方法method,請(qǐng)求頭headers,請(qǐng)求體RequestBody,tag標(biāo)簽等。
  • Response作為相應(yīng)信息的封裝,內(nèi)部包含對(duì)應(yīng)的請(qǐng)求信息request,http協(xié)議protocol,響應(yīng)碼code,響應(yīng)頭headers,響應(yīng)消息message,響應(yīng)體ResponseBody等。
  • Call作為一個(gè)請(qǐng)求的接口,提供獲取對(duì)應(yīng)請(qǐng)求信息息request,執(zhí)行請(qǐng)求execute,異步請(qǐng)求入隊(duì)enqueue,取消請(qǐng)求cancel等方法的定義。
  • RealCall是Call請(qǐng)求的具體實(shí)現(xiàn),實(shí)現(xiàn)了同步請(qǐng)求執(zhí)行,異步請(qǐng)求入隊(duì),請(qǐng)求取消等操作。同時(shí)內(nèi)部提供ApplicationInterceptorChain負(fù)責(zé)請(qǐng)求和響應(yīng)的攔截處理。
  • AsynCall是一個(gè)Runnable異步請(qǐng)求任務(wù),分發(fā)器Dispatcher負(fù)責(zé)管理這些異步請(qǐng)求,在可請(qǐng)求數(shù)量滿(mǎn)足的情況下會(huì)交給線(xiàn)程池執(zhí)行它的execute方法。

Dispatcher請(qǐng)求調(diào)用分發(fā)器

Dispatcher是請(qǐng)求Call的分發(fā)器,用于管理請(qǐng)求的等待,執(zhí)行,取消。分為同步請(qǐng)求RealCall和異步請(qǐng)求AsyncCall的管理。

  • 針對(duì)同步請(qǐng)求,用runningSyncCalls隊(duì)列記錄正在執(zhí)行的同步請(qǐng)求。
  • 針對(duì)異步請(qǐng)求,用readyAsyncCalls(待執(zhí)行的異步請(qǐng)求隊(duì)列)和runningAsyncCalls(正在執(zhí)行的異步請(qǐng)求隊(duì)列)記錄異步請(qǐng)求。
  • 內(nèi)部提供線(xiàn)程池,負(fù)責(zé)執(zhí)行異步請(qǐng)求,查看executorService()。
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;
}

這個(gè)線(xiàn)程池,我們看參數(shù)解析:

1. 參數(shù)0,代表核心線(xiàn)程的數(shù)量,即線(xiàn)程池中最少線(xiàn)程時(shí)的數(shù)量(當(dāng)所有線(xiàn)程都空閑時(shí)),為0的話(huà),說(shuō)明線(xiàn)程空閑時(shí),不保留任何線(xiàn)程,做到線(xiàn)程低占用。
2. 參數(shù)Integer.MAX_VALUE,代表最大線(xiàn)程數(shù),即線(xiàn)程池中最多線(xiàn)程時(shí)的數(shù)量,為Integer.MAX_VALUE的話(huà),說(shuō)明可以開(kāi)啟無(wú)限多的線(xiàn)程進(jìn)行工作,線(xiàn)程工作完了再關(guān)閉。
3. 參數(shù)60,和參數(shù)TimeUnit.SECONDS,表示當(dāng)線(xiàn)程池的數(shù)量比核心線(xiàn)程的數(shù)量大時(shí),等待60秒之后就會(huì)去關(guān)閉空閑線(xiàn)程,使總的線(xiàn)程數(shù)量不會(huì)大于核心線(xiàn)程數(shù)量。
4. 參數(shù)new SynchronousQueue<Runnable>(), 代表這個(gè)線(xiàn)程等待隊(duì)列是同步隊(duì)列,當(dāng)有一個(gè)線(xiàn)程進(jìn)來(lái)時(shí),就同時(shí)會(huì)有一個(gè)線(xiàn)程出去,也就是說(shuō)線(xiàn)程不會(huì)停留在其中,一進(jìn)入就立馬出去執(zhí)行,這種方式在高頻請(qǐng)求時(shí)是很合適的。
5. 參數(shù)Util.threadFactory("OkHttp Dispatcher", false),表示提供一個(gè)線(xiàn)程工廠,用于創(chuàng)建新的線(xiàn)程。

這個(gè)線(xiàn)程池的設(shè)計(jì),是需要時(shí)可以創(chuàng)建無(wú)限多的線(xiàn)程,不需要時(shí)不保留任何線(xiàn)程,空閑線(xiàn)程60秒后如果還是空閑就會(huì)回收,以保證高阻塞低占用的使用。

  • 設(shè)置有maxRequests(最大異步請(qǐng)求的數(shù)量)和maxRequestsPerHost(單個(gè)服務(wù)器的最大異步請(qǐng)求數(shù)量),這是為了限制同時(shí)執(zhí)行的總的請(qǐng)求數(shù)量和針對(duì)同一個(gè)服務(wù)器訪問(wèn)的請(qǐng)求數(shù)量,如果有超過(guò)了這兩個(gè)的限制,就將異步請(qǐng)求AsyncCall添加到readyAsyncCalls(待執(zhí)行的異步請(qǐng)求隊(duì)列)去等待執(zhí)行。這兩個(gè)參數(shù)可以配置。
  • enqueue方法,AsyncCall入隊(duì)操作
synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      //未超過(guò)限制,執(zhí)行加入到線(xiàn)程池執(zhí)行異步請(qǐng)求
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      //超過(guò)限制,將行異步請(qǐng)求加入等待隊(duì)列,等待執(zhí)行
      readyAsyncCalls.add(call);
    }
}
  • finished方法,標(biāo)記當(dāng)前AsyncCall已經(jīng)完成,這時(shí)會(huì)考慮從異步等待隊(duì)列取出請(qǐng)求去執(zhí)行。
//標(biāo)記請(qǐng)求完成
synchronized void finished(AsyncCall call) {
    if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
    promoteCalls();
}

//從異步等待隊(duì)列取出請(qǐng)求去執(zhí)行,同時(shí)記錄到正在執(zhí)行的異步隊(duì)列中
private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();
    
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }
    
      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
}
  • cancelAll方法,取消所有請(qǐng)求,包括所有同步請(qǐng)求,所有正在執(zhí)行的異步請(qǐng)求,所有等待執(zhí)行的異步請(qǐng)求。
public synchronized void cancelAll() {
    for (AsyncCall call : readyAsyncCalls) {
      call.cancel();
    }
    
    for (AsyncCall call : runningAsyncCalls) {
      call.cancel();
    }
    
    for (RealCall call : runningSyncCalls) {
      call.cancel();
    }
}

RealCall 真正執(zhí)行請(qǐng)求調(diào)用的地方

RealCall是真正執(zhí)行請(qǐng)求調(diào)用的入口,無(wú)論是同步請(qǐng)求的execute,還是異步請(qǐng)求的enqueue(由Dispatcher進(jìn)行分發(fā)),最終都會(huì)到RealCall的getResponseWithInterceptorChain。

QQ截圖20171102182751.png

getResponseWithInterceptorChain里創(chuàng)建了一個(gè)ApplicationInterceptorChain攔截鏈來(lái)處理當(dāng)前RealCall中的Request請(qǐng)求數(shù)據(jù)。接下來(lái)講講Okhttp中攔截器是這樣的一種運(yùn)行模式。

Interceptors攔截器原理

Interceptors攔截器采用了責(zé)任鏈模式一層一層的處理請(qǐng)求響應(yīng)信息。這里我們從同步請(qǐng)求去分析添加了HttpLoggingInterceptor日志打印攔截器的運(yùn)行原理。

final class RealCall implements Call {

  //執(zhí)行請(qǐng)求,這里創(chuàng)建了ApplicationInterceptorChain攔截鏈,負(fù)責(zé)對(duì)所有攔截器進(jìn)行調(diào)用,調(diào)用proceed開(kāi)始處理攔截
  private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
    Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
    return chain.proceed(originalRequest);
  }

  class ApplicationInterceptorChain implements Interceptor.Chain {
    private final int index;
    private final Request request;
    private final boolean forWebSocket;

    ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {
      this.index = index;
      this.request = request;
      this.forWebSocket = forWebSocket;
    }

    @Override public Connection connection() {
      return null;
    }

    @Override public Request request() {
      return request;
    }

    //這里遍歷攔截器,通過(guò)index在攔截鏈中找到對(duì)應(yīng)的攔截器,然后調(diào)用intercept進(jìn)行攔截處理,返回加工后的Response響應(yīng)信息
    @Override public Response proceed(Request request) throws IOException {
      // If there's another interceptor in the chain, call that.
      if (index < client.interceptors().size()) {
        Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
        Interceptor interceptor = client.interceptors().get(index);
        Response interceptedResponse = interceptor.intercept(chain);

        if (interceptedResponse == null) {
          throw new NullPointerException("application interceptor " + interceptor
              + " returned null");
        }

        return interceptedResponse;
      }

      //當(dāng)所有攔截器都遍歷處理后,開(kāi)始執(zhí)行真正請(qǐng)求,返回Response,這里才是真正產(chǎn)生Response響應(yīng)的地方。
      // No more interceptors. Do HTTP.
      return getResponse(request, forWebSocket);
    }
  }

}

看了以上代碼之后,我們知道是通過(guò)index一個(gè)一個(gè)遍歷攔截鏈的攔截器,去攔截處理,這是一個(gè)遞歸的過(guò)程,當(dāng)所有的攔截器處理了Request請(qǐng)求信息之后(也就是真正請(qǐng)求之前的預(yù)處理,比如打日志,修改請(qǐng)求頭什么的),才真正的交給網(wǎng)絡(luò)請(qǐng)求引擎去執(zhí)行請(qǐng)求,返回響應(yīng)信息,然后又按相反順序?qū)esponse響應(yīng)信息進(jìn)行攔截處理(也就是對(duì)響應(yīng)信息的再加工,比如打印響應(yīng)信息等)。那么我們分析HttpLoggingInterceptor日志打印攔截器會(huì)更加的的清晰這個(gè)過(guò)程。

public final class HttpLoggingInterceptor implements Interceptor {

    //攔截鏈上的
    @Override public Response intercept(Chain chain) throws IOException {
    Level level = this.level;

    //獲取請(qǐng)求信息
    Request request = chain.request();
    
    //如果不打印日志,那就直接調(diào)用chain.proceed執(zhí)行下一個(gè)攔截操作,不做其他操作。
    if (level == Level.NONE) {
      return chain.proceed(request);
    }

    //這部分是打印請(qǐng)求信息,對(duì)請(qǐng)求進(jìn)行預(yù)處理(這里可以對(duì)請(qǐng)求信息進(jìn)行修改或做其他操作)
    ---------------------------------------
    boolean logBody = level == Level.BODY;
    boolean logHeaders = logBody || level == Level.HEADERS;

    RequestBody requestBody = request.body();
    boolean hasRequestBody = requestBody != null;

    Connection connection = chain.connection();
    Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1;
    String requestStartMessage = "--> " + request.method() + ' ' + request.url() + ' ' + protocol;
    if (!logHeaders && hasRequestBody) {
      requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
    }
    logger.log(requestStartMessage);

    if (logHeaders) {
      if (hasRequestBody) {
        // Request body headers are only present when installed as a network interceptor. Force
        // them to be included (when available) so there values are known.
        if (requestBody.contentType() != null) {
          logger.log("Content-Type: " + requestBody.contentType());
        }
        if (requestBody.contentLength() != -1) {
          logger.log("Content-Length: " + requestBody.contentLength());
        }
      }

      Headers headers = request.headers();
      for (int i = 0, count = headers.size(); i < count; i++) {
        String name = headers.name(i);
        // Skip headers from the request body as they are explicitly logged above.
        if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
          logger.log(name + ": " + headers.value(i));
        }
      }

      if (!logBody || !hasRequestBody) {
        logger.log("--> END " + request.method());
      } else if (bodyEncoded(request.headers())) {
        logger.log("--> END " + request.method() + " (encoded body omitted)");
      } else {
        Buffer buffer = new Buffer();
        requestBody.writeTo(buffer);

        Charset charset = UTF8;
        MediaType contentType = requestBody.contentType();
        if (contentType != null) {
          charset = contentType.charset(UTF8);
        }

        logger.log("");
        logger.log(buffer.readString(charset));

        logger.log("--> END " + request.method()
            + " (" + requestBody.contentLength() + "-byte body)");
      }
    }
    //請(qǐng)求預(yù)處理完成
    ----------------------------------
    
    //這里調(diào)用chain.proceed執(zhí)行下一個(gè)攔截操作,返回Response響應(yīng)信息,結(jié)合ApplicationInterceptorChain的proceed方法,很容易看出是一個(gè)責(zé)任鏈的遞歸調(diào)用模式
    long startNs = System.nanoTime();
    Response response = chain.proceed(request);
    long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);

    //接下來(lái)打印響應(yīng)信息,對(duì)Response響應(yīng)進(jìn)行再加工(這里可以對(duì)響應(yīng)信息進(jìn)行修改或做其他操作)
    ---------------------------------------
    ResponseBody responseBody = response.body();
    long contentLength = responseBody.contentLength();
    String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
    logger.log("<-- " + response.code() + ' ' + response.message() + ' '
        + response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", "
        + bodySize + " body" : "") + ')');

    if (logHeaders) {
      Headers headers = response.headers();
      for (int i = 0, count = headers.size(); i < count; i++) {
        logger.log(headers.name(i) + ": " + headers.value(i));
      }

      if (!logBody || !HttpEngine.hasBody(response)) {
        logger.log("<-- END HTTP");
      } else if (bodyEncoded(response.headers())) {
        logger.log("<-- END HTTP (encoded body omitted)");
      } else {
        BufferedSource source = responseBody.source();
        source.request(Long.MAX_VALUE); // Buffer the entire body.
        Buffer buffer = source.buffer();

        Charset charset = UTF8;
        MediaType contentType = responseBody.contentType();
        if (contentType != null) {
          try {
            charset = contentType.charset(UTF8);
          } catch (UnsupportedCharsetException e) {
            logger.log("");
            logger.log("Couldn't decode the response body; charset is likely malformed.");
            logger.log("<-- END HTTP");

            return response;
          }
        }

        if (contentLength != 0) {
          logger.log("");
          logger.log(buffer.clone().readString(charset));
        }

        logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
      }
    }
    //響應(yīng)再加工完成
    ----------------------------------

    //這里返回響應(yīng)信息
    return response;
  }
}

明白攔截器的運(yùn)行模式之后,我們知道真正的請(qǐng)求是在RealCall的getResponse方法中開(kāi)始的。

/**
* Performs the request and returns the response. May return null if this call was canceled.
*/
Response getResponse(Request request, boolean forWebSocket) throws IOException {
    //這里負(fù)責(zé)執(zhí)行請(qǐng)求,然后返回響應(yīng)數(shù)據(jù)
    ...
}

具體請(qǐng)求我們下節(jié)分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,155評(píng)論 3 425
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,635評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,539評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,255評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,646評(píng)論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,838評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,399評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,146評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,338評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,565評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,983評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,257評(píng)論 1 292
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,059評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,296評(píng)論 2 376

推薦閱讀更多精彩內(nèi)容

  • OkHttp解析系列 OkHttp解析(一)從用法看清原理OkHttp解析(二)網(wǎng)絡(luò)連接OkHttp解析(三)關(guān)于...
    Hohohong閱讀 20,989評(píng)論 4 58
  • OkHttp源碼的samples的簡(jiǎn)單使用的示例: public static void main(String....
    _warren閱讀 800評(píng)論 0 1
  • 關(guān)于okhttp是一款優(yōu)秀的網(wǎng)絡(luò)請(qǐng)求框架,關(guān)于它的源碼分析文章有很多,這里分享我在學(xué)習(xí)過(guò)程中讀到的感覺(jué)比較好的文章...
    蕉下孤客閱讀 3,613評(píng)論 2 38
  • 這篇文章主要講 Android 網(wǎng)絡(luò)請(qǐng)求時(shí)所使用到的各個(gè)請(qǐng)求庫(kù)的關(guān)系,以及 OkHttp3 的介紹。(如理解有誤,...
    小莊bb閱讀 1,173評(píng)論 0 4
  • 這段時(shí)間老李的新公司要更換網(wǎng)絡(luò)層,知道現(xiàn)在主流網(wǎng)絡(luò)層的模式是RxJava+Retrofit+OKHttp,所以老李...
    隔壁老李頭閱讀 33,048評(píng)論 51 405