談談OKHttp的幾道面試題

來吧肴颊,今天說說常用的網絡框架OKHttp对室,也是現在Android所用的原生網絡框架(Android 4.4開始结窘,HttpURLConnection的底層實現被Google改成了OkHttp)各薇,GOGOGO坟瓢!

  • OKHttp有哪些攔截器操禀,分別起什么作用
  • OkHttp怎么實現連接池
  • OkHttp里面用到了什么設計模式

OKHttp有哪些攔截器褂策,分別起什么作用

OKHTTP的攔截器是把所有的攔截器放到一個list里,然后每次依次執(zhí)行攔截器,并且在每個攔截器分成三部分:

  • 預處理攔截器內容
  • 通過proceed方法把請求交給下一個攔截器
  • 下一個攔截器處理完成并返回斤寂,后續(xù)處理工作耿焊。

這樣依次下去就形成了一個鏈式調用,看看源碼遍搞,具體有哪些攔截器:

  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);
  }

根據源碼可知罗侯,一共七個攔截器:

  • addInterceptor(Interceptor),這是由開發(fā)者設置的溪猿,會按照開發(fā)者的要求钩杰,在所有的攔截器處理之前進行最早的攔截處理,比如一些公共參數诊县,Header都可以在這里添加讲弄。
  • RetryAndFollowUpInterceptor,這里會對連接做一些初始化工作翎冲,以及請求失敗的充實工作垂睬,重定向的后續(xù)請求工作。跟他的名字一樣抗悍,就是做重試工作還有一些連接跟蹤工作驹饺。
  • BridgeInterceptor,這里會為用戶構建一個能夠進行網絡訪問的請求缴渊,同時后續(xù)工作將網絡請求回來的響應Response轉化為用戶可用的Response赏壹,比如添加文件類型,content-length計算添加衔沼,gzip解包蝌借。
  • CacheInterceptor,這里主要是處理cache相關處理指蚁,會根據OkHttpClient對象的配置以及緩存策略對請求值進行緩存菩佑,而且如果本地有了可?的Cache,就可以在沒有網絡交互的情況下就返回緩存結果凝化。
  • ConnectInterceptor稍坯,這里主要就是負責建立連接了,會建立TCP連接或者TLS連接搓劫,以及負責編碼解碼的HttpCodec
  • networkInterceptors瞧哟,這里也是開發(fā)者自己設置的,所以本質上和第一個攔截器差不多枪向,但是由于位置不同勤揩,所以用處也不同。這個位置添加的攔截器可以看到請求和響應的數據了秘蛔,所以可以做一些網絡調試陨亡。
  • CallServerInterceptor傍衡,這里就是進行網絡數據的請求和響應了,也就是實際的網絡I/O操作数苫,通過socket讀寫數據聪舒。

OkHttp怎么實現連接池

  • 為什么需要連接池?

頻繁的進行建立Sokcet連接和斷開Socket是非常消耗網絡資源和浪費時間的虐急,所以HTTP中的keepalive連接對于降低延遲和提升速度有非常重要的作用箱残。keepalive機制是什么呢?也就是可以在一次TCP連接中可以持續(xù)發(fā)送多份數據而不會斷開連接止吁。所以連接的多次使用被辑,也就是復用就變得格外重要了,而復用連接就需要對連接進行管理敬惦,于是就有了連接池的概念盼理。

OkHttp中使用ConectionPool實現連接池,默認支持5個并發(fā)KeepAlive俄删,默認鏈路生命為5分鐘宏怔。

  • 怎么實現的?

1)首先畴椰,ConectionPool中維護了一個雙端隊列Deque臊诊,也就是兩端都可以進出的隊列,用來存儲連接斜脂。
2)然后在ConnectInterceptor抓艳,也就是負責建立連接的攔截器中,首先會找可用連接帚戳,也就是從連接池中去獲取連接玷或,具體的就是會調用到ConectionPool的get方法。

RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (connection.isEligible(address, route)) {
        streamAllocation.acquire(connection, true);
        return connection;
      }
    }
    return null;
  }

也就是遍歷了雙端隊列片任,如果連接有效偏友,就會調用acquire方法計數并返回這個連接。

3)如果沒找到可用連接对供,就會創(chuàng)建新連接位他,并會把這個建立的連接加入到雙端隊列中,同時開始運行線程池中的線程犁钟,其實就是調用了ConectionPool的put方法棱诱。

public final class ConnectionPool {
    void put(RealConnection connection) {
        if (!cleanupRunning) {
            //沒有連接的時候調用
            cleanupRunning = true;
            executor.execute(cleanupRunnable);
        }
        connections.add(connection);
    }
}

3)其實這個線程池中只有一個線程泼橘,是用來清理連接的涝动,也就是上述的cleanupRunnable

private final Runnable cleanupRunnable = new Runnable() {
        @Override
        public void run() {
            while (true) {
                //執(zhí)行清理,并返回下次需要清理的時間炬灭。
                long waitNanos = cleanup(System.nanoTime());
                if (waitNanos == -1) return;
                if (waitNanos > 0) {
                    long waitMillis = waitNanos / 1000000L;
                    waitNanos -= (waitMillis * 1000000L);
                    synchronized (ConnectionPool.this) {
                        //在timeout時間內釋放鎖
                        try {
                            ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                        } catch (InterruptedException ignored) {
                        }
                    }
                }
            }
        }
    };

這個runnable會不停的調用cleanup方法清理線程池醋粟,并返回下一次清理的時間間隔靡菇,然后進入wait等待。

怎么清理的呢米愿?看看源碼:

long cleanup(long now) {
    synchronized (this) {
      //遍歷連接
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();

        //檢查連接是否是空閑狀態(tài)厦凤,
        //不是,則inUseConnectionCount + 1
        //是 育苟,則idleConnectionCount + 1
        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;
          continue;
        }

        idleConnectionCount++;

        // If the connection is ready to be evicted, we're done.
        long idleDurationNs = now - connection.idleAtNanos;
        if (idleDurationNs > longestIdleDurationNs) {
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      }

      //如果超過keepAliveDurationNs或maxIdleConnections较鼓,
      //從雙端隊列connections中移除
      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {      
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {      //如果空閑連接次數>0,返回將要到期的時間
        // A connection will be ready to evict soon.
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {
        // 連接依然在使用中,返回保持連接的周期5分鐘
        return keepAliveDurationNs;
      } else {
        // No connections, idle or in use.
        cleanupRunning = false;
        return -1;
      }
    }

    closeQuietly(longestIdleConnection.socket());

    // Cleanup again immediately.
    return 0;
  }

也就是當如果空閑連接maxIdleConnections超過5個或者keepalive時間大于5分鐘违柏,則將該連接清理掉博烂。

4)這里有個問題,怎樣屬于空閑連接漱竖?

其實就是有關剛才說到的一個方法acquire計數方法:

  public void acquire(RealConnection connection, boolean reportedAcquired) {
    assert (Thread.holdsLock(connectionPool));
    if (this.connection != null) throw new IllegalStateException();

    this.connection = connection;
    this.reportedAcquired = reportedAcquired;
    connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
  }

RealConnection中禽篱,有一個StreamAllocation虛引用列表allocations。每創(chuàng)建一個連接馍惹,就會把連接對應的StreamAllocationReference添加進該列表中躺率,如果連接關閉以后就將該對象移除。

5)連接池的工作就這么多万矾,并不復雜悼吱,主要就是管理雙端隊列Deque<RealConnection>,可以用的連接就直接用勤众,然后定期清理連接舆绎,同時通過對StreamAllocation的引用計數實現自動回收。

OkHttp里面用到了什么設計模式

  • 責任鏈模式

這個不要太明顯们颜,可以說是okhttp的精髓所在了吕朵,主要體現就是攔截器的使用,具體代碼可以看看上述的攔截器介紹窥突。

  • 建造者模式

在Okhttp中努溃,建造者模式也是用的挺多的,主要用處是將對象的創(chuàng)建與表示相分離阻问,用Builder組裝各項配置梧税。
比如Request:

public class Request {
  public static class Builder {
    @Nullable HttpUrl url;
    String method;
    Headers.Builder headers;
    @Nullable RequestBody body;
    public Request build() {
      return new Request(this);
    }
  }
}
  • 工廠模式

工廠模式和建造者模式類似,區(qū)別就在于工廠模式側重點在于對象的生成過程称近,而建造者模式主要是側重對象的各個參數配置第队。
例子有CacheInterceptor攔截器中又個CacheStrategy對象:

    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

    public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;
      this.request = request;
      this.cacheResponse = cacheResponse;

      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            ageSeconds = HttpHeaders.parseSeconds(value, -1);
          }
        }
      }
    }

  • 觀察者模式

之前我寫過一篇文章,是關于Okhttp中websocket的使用刨秆,由于webSocket屬于長連接凳谦,所以需要進行監(jiān)聽,這里是用到了觀察者模式:

  final WebSocketListener listener;
  @Override public void onReadMessage(String text) throws IOException {
    listener.onMessage(this, text);
  }

  • 單例模式

這個就不舉例了衡未,每個項目都會有

  • 另外有的博客還說到了策略模式尸执,門面模式等家凯,這些大家可以網上搜搜,畢竟每個人的想法看法都會不同如失,細心找找可能就會發(fā)現绊诲。

拜拜

有一起學習的小伙伴可以關注下??我的公眾號——碼上積木,每天剖析一個知識點褪贵,我們一起積累知識掂之。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市脆丁,隨后出現的幾起案子板惑,更是在濱河造成了極大的恐慌,老刑警劉巖偎快,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冯乘,死亡現場離奇詭異,居然都是意外死亡晒夹,警方通過查閱死者的電腦和手機裆馒,發(fā)現死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丐怯,“玉大人喷好,你說我怎么就攤上這事《刘危” “怎么了梗搅?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長效览。 經常有香客問我无切,道長,這世上最難降的妖魔是什么丐枉? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任哆键,我火速辦了婚禮,結果婚禮上瘦锹,老公的妹妹穿的比我還像新娘籍嘹。我一直安慰自己,他們只是感情好弯院,可當我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布辱士。 她就那樣靜靜地躺著,像睡著了一般听绳。 火紅的嫁衣襯著肌膚如雪颂碘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天辫红,我揣著相機與錄音凭涂,去河邊找鬼。 笑死贴妻,一個胖子當著我的面吹牛切油,可吹牛的內容都是我干的。 我是一名探鬼主播名惩,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼澎胡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了娩鹉?” 一聲冷哼從身側響起攻谁,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弯予,沒想到半個月后戚宦,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡锈嫩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年受楼,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呼寸。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡艳汽,死狀恐怖,靈堂內的尸體忽然破棺而出对雪,到底是詐尸還是另有隱情河狐,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站劫狠,受9級特大地震影響勃救,放射性物質發(fā)生泄漏。R本人自食惡果不足惜丈钙,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望交汤。 院中可真熱鬧雏赦,春花似錦、人聲如沸芙扎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽戒洼。三九已至俏橘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間圈浇,已是汗流浹背寥掐。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工靴寂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人召耘。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓百炬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親污它。 傳聞我的和親對象是個殘疾皇子剖踊,可洞房花燭夜當晚...
    茶點故事閱讀 44,974評論 2 355