Android開源框架之OkHttp

OkHttp相信搞android的都不陌生,它是目前應(yīng)用最多的網(wǎng)絡(luò)請求開源框架须床,雖然現(xiàn)在Retrofit更加流行铐料,但到底層其實也是基于OkHttp的。你要是說你沒用過OkHttp都不好意思說自己是做過Android開發(fā)豺旬。那么今天就來聊聊OkHttp余赢。
本文的要點如下:

  • 概述
  • OkHttp的使用
  • 源碼分析
    • 同步請求
    • 異步請求
  • 總結(jié)

概述

OkHttp是一個網(wǎng)絡(luò)請求開源庫,即將網(wǎng)絡(luò)請求的相關(guān)功能封裝好的類庫哈垢。要知道妻柒,沒有網(wǎng)絡(luò)請求框架之前,App想與服務(wù)器進(jìn)行網(wǎng)絡(luò)請求交互是一件很痛苦的事耘分,因為Android的主線程不能進(jìn)行耗時操作举塔,那么就需另開1個線程請求、考慮到線程池求泰,緩存等一堆問題央渣。
于是乎,網(wǎng)絡(luò)請求庫出現(xiàn)了渴频,網(wǎng)絡(luò)請求庫的本質(zhì)其實是封裝了 網(wǎng)絡(luò)請求 + 異步 + 數(shù)據(jù)處理功能的庫芽丹。

OkHttp的使用

同步請求:

GET請求:

    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder()
            .url(url)
            .build();
    Response response = client.newCall(request).execute();
    return response.body().string();

POST請求:

    OkHttpClient client = new OkHttpClient();
    RequestBody requestBody = new FormBody.Builder()
              .add("username","abc")
              .add("password","123456")
              .build();
    Request request = new Request.Builder()
              .url("http://www.baidu.com")
              .post(requestBody)
              .build();
    Response response = client.newCall(request).execute();
    return response.body().string();

異步請求(以GET為例):

  OkHttpClient client = new OkHttpClient();
  Request request = new Request.Builder()
            .url(url)
            .build();
  client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        System.out.println(response.body().string());
    }
});

使用這一塊沒什么好講的,不同類型的請求方式有些許的不同卜朗,不過都比較好理解拔第。下面我們還是來看看源碼中究竟是怎么做的吧咕村。

源碼分析

ps:我用的源碼是OkHttp3.14.1。
首先蚊俺,使用OkHttp時懈涛,必然要先創(chuàng)建OkHttpClient對象。

 OkHttpClient client = new OkHttpClient();

一行代碼就搞定了泳猬,似乎有點過于簡單了批钠,我們來看看OkHttpClient()里面做了什么:

  public OkHttpClient() {
    this(new Builder());
  }

原來是方便我們使用,提供了一個默認(rèn)的配置得封,直接傳入了一個默認(rèn)的Builder類型的對象埋心。Builder在初始化時就會設(shè)置這一些參數(shù),全都是默認(rèn)值忙上。這里就是運用了Builder模式踩窖,簡化了構(gòu)建過程。

public Builder() {
      dispatcher = new Dispatcher();//調(diào)度器
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
      cookieJar = CookieJar.NO_COOKIES;
      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;
      callTimeout = 0;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

同步請求

盡管現(xiàn)在項目中很少用同步請求了晨横,但是其實異步請求的基礎(chǔ)還是同步請求洋腮,只不過中間轉(zhuǎn)換了線程而已。
在同步請求手形,初始化之后啥供,我們又用Builder構(gòu)建了Request對象,然后執(zhí)行了OKHttpClient的newCall方法库糠,那么咱們就看看這個newCall里面都做什么操作伙狐?

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

可以看出client.newCall(request).execute();實際上執(zhí)行的是RealCall的execute方法,現(xiàn)在咱們再回來看下RealCall的execute的具體實現(xiàn)瞬欧。

@Override 
public Response execute() throws IOException {
    synchronized (this) {
      //同步判斷贷屎,保證每個call只用一次,重復(fù)使用會拋出異常
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.timeoutEnter();
    transmitter.callStart();
    try {
      client.dispatcher().executed(this);
      return getResponseWithInterceptorChain();
    } finally {
      client.dispatcher().finished(this);
    }
  }

這里主要做了 4 件事:

  1. 檢查這個 call 是否已經(jīng)被執(zhí)行了艘虎,每個 call 只能被執(zhí)行一次唉侄,如果想要一個完全一樣的 call,可以利用call#clone方法進(jìn)行克隆野建。
  2. 利用client.dispatcher().executed(this)來進(jìn)行實際執(zhí)行属划,dispatcher是剛才看到的OkHttpClient.Builder的成員之一。
  3. 調(diào)用getResponseWithInterceptorChain()函數(shù)獲取 HTTP 返回結(jié)果候生,從函數(shù)名也可以看出同眯,這一步還會進(jìn)行一系列“攔截”操作。
  4. 最后還要通知dispatcher自己已經(jīng)執(zhí)行完畢唯鸭,釋放dispatcher须蜗。

那么我們看下dispatcher里面的execute()是如何處理的。

    synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);
    }

可以看到,其實也很簡單明肮,runningSyncCalls執(zhí)行了add方法菱农,添加的參數(shù)是RealCall。runningSyncCalls是什么呢晤愧?

/** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

可以看到runningSyncCalls是雙向隊列大莫。另外蛉腌,我們發(fā)現(xiàn)Dispatcher里面定義了三個雙向隊列官份,看下注釋,我們大概能明白readyAsyncCalls是一個存放了等待執(zhí)行任務(wù)Call的雙向隊列烙丛,runningAsyncCalls是一個存放異步請求任務(wù)Call的雙向任務(wù)隊列舅巷,runningSyncCalls是一個存放同步請求的雙向隊列
那么這一步的目的就是將RealCall放入同步隊列中河咽。

回到之前钠右,執(zhí)行完client.dispatcher().executed()方法,要執(zhí)行g(shù)etResponseWithInterceptorChain()方法忘蟹,這就是OkHttp的核心攔截器的工作了:

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //添加開發(fā)者應(yīng)用層自定義的Interceptor
    interceptors.addAll(client.interceptors());
    //這個Interceptor是處理請求失敗的重試飒房,重定向    
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    //這個Interceptor工作是添加一些請求的頭部或其他信息
    //并對返回的Response做一些友好的處理(有一些信息你可能并不需要)
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //這個Interceptor的職責(zé)是判斷緩存是否存在,讀取緩存媚值,更新緩存等等
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //這個Interceptor的職責(zé)是建立客戶端和服務(wù)器的連接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //添加開發(fā)者自定義的網(wǎng)絡(luò)層攔截器
      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 {
      //把chain傳遞到第一個Interceptor手中
      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);
      }
    }
  }

可以看到狠毯,主要的操作就是new了一個ArrayList,然后就是不斷的add攔截器Interceptor褥芒,之后new了一個RealInterceptorChain對象嚼松,最后調(diào)用了chain.proceed()方法。

我們來看看RealInterceptorChain()構(gòu)造方法锰扶。

public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, Connection connection, int index, Request request) {
    this.interceptors = interceptors;//將攔截鏈保存
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
  }

就是一些賦值操作献酗,將信息保存,關(guān)鍵的是this.interceptors = interceptors這里就保存了攔截鏈坷牛。

之后我們來看一下chain.proceed()方法獲取返回的信息罕偎。由于Interceptor是個接口,所以應(yīng)該是具體實現(xiàn)類RealInterceptorChain的proceed實現(xiàn)京闰。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
    //省略其他代碼   
    calls++;
    RealInterceptorChain next = new RealInterceptorChain(
    interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    //省略其他代碼   
    return response;
}

然后看到在proceed方面里面又new了一個RealInterceptorChain類的next對象锨亏,這個next對象和chain最大的區(qū)別就是index屬性值不同chain是0,而next是1,然后取interceptors下標(biāo)為1的對象的interceptor忙干。由從上文可知器予,如果沒有開發(fā)者自定義的應(yīng)用層Interceptor時,首先調(diào)用的RetryAndFollowUpInterceptor捐迫,如果有開發(fā)者自己定義的應(yīng)用層interceptor則調(diào)用開發(fā)者interceptor乾翔。

后面的流程都差不多,在每一個interceptor的intercept方法里面都會調(diào)用chain.proceed()從而調(diào)用下一個interceptor的intercept(next)方法,這樣就可以實現(xiàn)遍歷getResponseWithInterceptorChain里面interceptors的item反浓,實現(xiàn)遍歷循環(huán)萌丈。

之前我們看過getResponseWithInterceptorChain里面interceptors的最后一個item是CallServerInterceptor,最后一個Interceptor(即CallServerInterceptor)里面是直接返回了response 而不是進(jìn)行繼續(xù)遞歸雷则。

CallServerInterceptor返回response后返回給上一個interceptor,一般是開發(fā)者自己定義的networkInterceptor辆雾,然后開發(fā)者自己的networkInterceptor把他的response返回給前一個interceptor,依次以此類推返回給第一個interceptor月劈,這時候又回到了realCall里面的execute()里面了度迂。

最后把response返回給get請求的返回值。至此同步GET請求的大體流程就已經(jīng)結(jié)束了猜揪。

異步請求

講了這么多同步請求惭墓,其實異步請求才是重頭戲,畢竟現(xiàn)在的項目中大多用的都是異步請求而姐。
由于前面的步驟和同步一樣new了一個OKHttp和Request腊凶。這塊和同步一樣就不說了,那么說說和同步不一樣的地方拴念,后面異步進(jìn)入的是newCall()的enqueue()方法钧萍。
之前分析過,newCall()里面是生成了一個RealCall對象政鼠,那么執(zhí)行的其實是RealCall的enqueue()方法风瘦。我們來看源碼:

 @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      //同步判斷,保證每個call只用一次缔俄,重復(fù)使用會拋出異常
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.callStart();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

可以看到和同步請求的enqueue方法一樣弛秋,還是先同步判斷是否被請求過了,不一樣的地方就在于調(diào)用了client.dispatcher().enqueue(new AsyncCall(responseCallback))方法俐载。即實際調(diào)用的是Dispatcher的enqueue()方法:

void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);//將call添加到了異步請求隊列

      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
      // the same host.
      if (!call.get().forWebSocket) {
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    promoteAndExecute();
  }

這里主要做了兩件事:

  1. 將call添加到了異步請求隊列蟹略;
  2. 調(diào)用promoteAndExecute方法。
private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();

        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
        asyncCall.callsPerHost().incrementAndGet();
        executableCalls.add(asyncCall);//將異步請求添加到executableCalls中
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }

    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }

可以看出先是迭代了上面的隊列,取出隊列里的AsyncCall后添加到了executableCalls集合中。然后遍歷這個集合雪情,開始執(zhí)行每個AsyncCall的executeOn方法。參數(shù)是executorService()意敛,我們來具體看看:

public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

不難看出,這個方法傳遞進(jìn)去的是ExecutorService線程池膛虫。那看來關(guān)鍵就在executeOn方法中了:

void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
        executorService.execute(this);//用executorService線程池執(zhí)行了當(dāng)前的線程
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        transmitter.noMoreExchanges(ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }

通過executorService線程池執(zhí)行了當(dāng)前的線程草姻,也就是AsyncCall,那么AsyncCall由于繼承了NamedRunnable稍刀,這個NamedRunnable的run方法里又執(zhí)行了抽象方法execute撩独,所以敞曹,實際上這里執(zhí)行了AsyncCall的execute方法。

@Override protected void execute() {
      boolean signalledCallback = false;
      transmitter.timeoutEnter();
      try {
        Response response = getResponseWithInterceptorChain();
        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);
      }
    }

終于综膀,我們看到了幾個熟悉的名字澳迫,getResponseWithInterceptorChain、onResponse和onFailure剧劝。不難看出橄登,這里依舊是用getResponseWithInterceptorChain()通過攔截鏈進(jìn)行請求,最終執(zhí)行結(jié)果回調(diào)給了我們傳遞進(jìn)去的Callback讥此。至此拢锹,異步請求的主要流程也分析完了。

對比一下同步請求和異步請求暂论,不難看出面褐,其實異步請求就是維護(hù)了一個線程池用于進(jìn)行請求拌禾,在請求完成之后回調(diào)我們一開始傳入的CallBack接口取胎。

總結(jié)

  1. OkHttpClient實現(xiàn)了Call.Factory,負(fù)責(zé)為Request創(chuàng)建Call湃窍;
  2. RealCall為具體的Call實現(xiàn)闻蛀,execute()為同步接口,通過getResponseWithInterceptorChain()函數(shù)實現(xiàn)您市;enqueue()為異步接口通過Dispatcher利用ExecutorService線程池實現(xiàn)觉痛,而最終進(jìn)行網(wǎng)絡(luò)請求時和同步接口一致,都是通過getResponseWithInterceptorChain()函數(shù)實現(xiàn)茵休;
  3. getResponseWithInterceptorChain()中利用攔截鏈機(jī)制薪棒,分層實現(xiàn)緩存、透明壓縮榕莺、網(wǎng)絡(luò) IO 等功能俐芯。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市钉鸯,隨后出現(xiàn)的幾起案子吧史,更是在濱河造成了極大的恐慌,老刑警劉巖唠雕,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贸营,死亡現(xiàn)場離奇詭異,居然都是意外死亡岩睁,警方通過查閱死者的電腦和手機(jī)钞脂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捕儒,“玉大人冰啃,你說我怎么就攤上這事。” “怎么了亿笤?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵翎迁,是天一觀的道長。 經(jīng)常有香客問我净薛,道長汪榔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任肃拜,我火速辦了婚禮痴腌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘燃领。我一直安慰自己士聪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布猛蔽。 她就那樣靜靜地躺著剥悟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪曼库。 梳的紋絲不亂的頭發(fā)上区岗,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音毁枯,去河邊找鬼慈缔。 笑死,一個胖子當(dāng)著我的面吹牛种玛,可吹牛的內(nèi)容都是我干的藐鹤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赂韵,長吁一口氣:“原來是場噩夢啊……” “哼娱节!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起右锨,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤括堤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后绍移,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悄窃,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年蹂窖,在試婚紗的時候發(fā)現(xiàn)自己被綠了轧抗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞬测,死狀恐怖横媚,靈堂內(nèi)的尸體忽然破棺而出纠炮,到底是詐尸還是另有隱情,我是刑警寧澤灯蝴,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布恢口,位于F島的核電站,受9級特大地震影響穷躁,放射性物質(zhì)發(fā)生泄漏耕肩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一问潭、第九天 我趴在偏房一處隱蔽的房頂上張望猿诸。 院中可真熱鬧,春花似錦狡忙、人聲如沸梳虽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窜觉。三九已至,卻和暖如春删顶,著一層夾襖步出監(jiān)牢的瞬間竖螃,已是汗流浹背淑廊。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工逗余, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人季惩。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓录粱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親画拾。 傳聞我的和親對象是個殘疾皇子啥繁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

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