OkHttp - 源碼解析

特點:
支持HTTP2/SPDY黑科技(Http2 優(yōu)化了頭部壓縮仓手,多路復用多個http請求共用一個TCP連接)
socket自動選擇最好路線巩趁,并支持自動重連
擁有自動維護的socket連接池堤器,減少握手次數(shù)
擁有隊列線程池谨胞,輕松寫并發(fā)
擁有Interceptors輕松處理請求與響應(比如透明GZIP壓縮,LOGGING)
實現(xiàn)基于Headers的緩存策略
內(nèi)部有幾個比較重要的類:

OkHttpClient:

老規(guī)矩還是從最簡單的代碼來分析一下


        OkHttpClient client = new OkHttpClient.Builder().build();
        Request request = new Request.Builder().
                url("https://www.baidu.com").
                build();
        Call call = client.newCall(request);
        try {
            //1.同步請求方式
            Response response = call.execute();
            //2.異步請求方式
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    // 獲取失敗
                }
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    // 獲取成功
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }

先看OkHttpClientBuild方法

 public Builder() {
      dispatcher = new Dispatcher();// 分發(fā)器  控制并發(fā) 保存了同步和異步的call(線程池執(zhí)行)
      protocols = DEFAULT_PROTOCOLS; // http協(xié)議
      connectionSpecs = DEFAULT_CONNECTION_SPECS; // 連接配置
      eventListenerFactory = EventListener.factory(EventListener.NONE); // 監(jiān)聽器
      proxySelector = ProxySelector.getDefault(); // 代理選擇
      cookieJar = CookieJar.NO_COOKIES;// 默認沒有cookie
      socketFactory = SocketFactory.getDefault(); 
      hostnameVerifier = OkHostnameVerifier.INSTANCE; 
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      coBnectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

Build 方法創(chuàng)建了默認的設(shè)置,當繼續(xù)調(diào)用build方法時會采用默認設(shè)置創(chuàng)建OkHttpClient對象扎阶。這時如果需要改變默認的屬性洛姑,可以這樣設(shè)置

new OkHttpClient.Builder().connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS).build();

Request

接著看Request

public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final @Nullable RequestBody body;
  final Object tag;
  private volatile CacheControl cacheControl; // Lazily initialized.
  }

看到這個類包含類請求的url 上沐,method,headers楞艾,body参咙,緩存控制等等龄广。
接著往下分析

Call

 @Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    final EventListener.Factory eventListenerFactory = client.eventListenerFactory();
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
    this.eventListener = eventListenerFactory.create(this);
  }

RealCall是實現(xiàn)Call的子類,真正進行操作的類

Response

首先看下同步請求的

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      // 這邊直接放入Running隊列中執(zhí)行
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

真正進行網(wǎng)絡(luò)請求的是Response result = getResponseWithInterceptorChain();

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    // 負責與服務器建立連接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

這些Interceptor完成了一系列的鏈式使用蕴侧,CallServerInterceptor負責向服務器請求數(shù)據(jù)是最后一個择同,這個類的intercept不會再次調(diào)用下一個Interceptorintercept方法。ConnectInterceptor負責建立連接净宵,具體怎么實現(xiàn)可以看下代碼敲才,不過肯定逃不過Soket的使用,再看下CallServerInterceptorintercept方法择葡。

 @Override public Response intercept(Chain chain) throws IOException {
  HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
  StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
  Request request = chain.request();

  long sentRequestMillis = System.currentTimeMillis();
  httpCodec.writeRequestHeaders(request);

  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();
  }

  httpCodec.finishRequest();

  Response response = httpCodec.readResponseHeaders()
      .request(request)
      .handshake(streamAllocation.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();

  if (!forWebSocket || response.code() != 101) {
    response = response.newBuilder()
        .body(httpCodec.openResponseBody(response))
        .build();
  }

  if ("close".equalsIgnoreCase(response.request().header("Connection"))
      || "close".equalsIgnoreCase(response.header("Connection"))) {
    streamAllocation.noNewStreams();
  }
  ...
  return response;
}

最后由HttpCodec發(fā)起網(wǎng)絡(luò)請求紧武。
然后再看下異步網(wǎng)絡(luò)請求,按照思路基本流程肯定相似敏储,完成異步操作肯定需要新開線程完成操作阻星。

 @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    // 先放入Ready隊列
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

看下AsyncCall 怎么包裝的

// Dispatcher
 synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      // 提交線程池執(zhí)行
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }
// AsyncCall 
final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          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);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }
// AsyncCall 繼承的NamedRunnable
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();
}

Response response = getResponseWithInterceptorChain();這個就和之前的同步請求一摸一樣。
梳理一下整個流程

整個流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末已添,一起剝皮案震驚了整個濱河市妥箕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌更舞,老刑警劉巖矾踱,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異疏哗,居然都是意外死亡呛讲,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門返奉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贝搁,“玉大人,你說我怎么就攤上這事芽偏±啄妫” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵污尉,是天一觀的道長膀哲。 經(jīng)常有香客問我,道長被碗,這世上最難降的妖魔是什么某宪? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮锐朴,結(jié)果婚禮上兴喂,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好衣迷,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布畏鼓。 她就那樣靜靜地躺著,像睡著了一般壶谒。 火紅的嫁衣襯著肌膚如雪云矫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天汗菜,我揣著相機與錄音让禀,去河邊找鬼。 笑死呵俏,一個胖子當著我的面吹牛堆缘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播普碎,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吼肥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了麻车?” 一聲冷哼從身側(cè)響起缀皱,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎动猬,沒想到半個月后啤斗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡赁咙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年钮莲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彼水。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡崔拥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凤覆,到底是詐尸還是另有隱情链瓦,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布盯桦,位于F島的核電站慈俯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拥峦。R本人自食惡果不足惜贴膘,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望事镣。 院中可真熱鬧步鉴,春花似錦揪胃、人聲如沸璃哟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽随闪。三九已至阳似,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铐伴,已是汗流浹背撮奏。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留当宴,地道東北人畜吊。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像户矢,于是被迫代替她去往敵國和親玲献。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354