OkHttp3源碼學習

簡書上這個博文分析的挺好:
http://www.lxweimin.com/p/aad5aacd79bf

下面是我自己關注的一些點而已:

ConnectionPool : 連接池
相同地址的連接, 有可能會復用.
首先注意到的是SynchronousQueue的使用, 這是一個同步隊列, 沒有容量, 如果同時有多個或者一個線程往隊列中添加數據, 那么都會阻塞, 不管是一個還是多個線程, 知道有一個消費者線程會從隊列中取(poll)數據, 這是根據是否公平的設置, 會把前面那幾個線程中的一個解除阻塞, poll出的數據就是那個線程要放進隊列的數據, 這樣一個生產者線程和一個消費者線程都離開了.
同樣, 如果有多個線程都要poll數據, 那么這幾個線程也要排隊請求, 可能公平, 可能不公平, 這個策略是在創建SynchronousQueue時傳如的唯一boolean參數決定的.

好了 , 那么在這里SynchronousQueue的用處是什么呢?
具體是為了實現一個newCachedThreadPool, 具體原理跟線程池新建線程的策略有關, 可以閱讀: http://dongxuan.iteye.com/blog/901689
簡單來說使用SynchronousQueue的原因就是為了使生產者無法往隊列中添加數據, 從而促使線程池產生新的消費線程, 并且保證了添加到隊列中的任務(runnable)的開始執行的順序.

但是這里new出來的線程池跟 Executors.newCachedThreadPool()方式創建出來的線程池, 在我看來沒有任何區別.
另外為什么要創建一個線程池來執行后臺cleanup工作? 真的有此必要嗎?
這個線程池對象只處理這么一個任務:cleanupRunnable 并且還只處理一次. why? 為什么要用線程池??

猜測可能的好處: 方便的創建后臺執行的線程, 設置線程名字;
ConnectionPool.put()是實例方法, 也就是說如果有多個地方創建了不同的ConnectionPool對象, 那么會有對應的多個cleanupRunnable任務對象, 都會添加到靜態實例executor線程池中.
這是一個多對1的關系.

終于明白用線程池的原因了, 對于每個連接池對象ConnectionPool 在某些時刻, cleanupRunnable是可以停止運行的, 比如長時間沒有連接了, 我還cleanup個毛了, 也就自己停止運行了:
具體代碼邏輯:
![](file:///D:/Documents/My Knowledge/temp/d96bd727-a095-4af8-89a0-ff0ddc641e53/128/index_files/6749534b-9ac9-46e6-8729-88df44cecf3b.png) ...

![](file:///D:/Documents/My Knowledge/temp/d96bd727-a095-4af8-89a0-ff0ddc641e53/128/index_files/468a45ad-5412-4083-9fa5-b62cdaeb3db1.png) 這個cleanup方法返回-1 標志這可以停止運行cleanupRunnable任務了; 簡書上這個博文分析的挺好:
http://www.lxweimin.com/p/aad5aacd79bf
http://blog.csdn.net/qq_31694651/article/details/52463078

對比glide和picasso的使用:
http://www.lxweimin.com/p/fc72001dc18d

下面是我自己關注的一些點而已:

ConnectionPool : 連接池
相同地址的連接, 有可能會復用.
首先注意到的是SynchronousQueue的使用, 這是一個同步隊列, 沒有容量, 如果同時有多個或者一個線程往隊列中添加數據, 那么都會阻塞, 不管是一個還是多個線程, 知道有一個消費者線程會從隊列中取(poll)數據, 這是根據是否公平的設置, 會把前面那幾個線程中的一個解除阻塞, poll出的數據就是那個線程要放進隊列的數據, 這樣一個生產者線程和一個消費者線程都離開了.
同樣, 如果有多個線程都要poll數據, 那么這幾個線程也要排隊請求, 可能公平, 可能不公平, 這個策略是在創建SynchronousQueue時傳如的唯一boolean參數決定的.

好了 , 那么在這里SynchronousQueue的用處是什么呢?
具體是為了實現一個newCachedThreadPool, 具體原理跟線程池新建線程的策略有關, 可以閱讀: http://dongxuan.iteye.com/blog/901689
簡單來說使用SynchronousQueue的原因就是為了使生產者無法往隊列中添加數據, 從而促使線程池產生新的消費線程, 并且保證了添加到隊列中的任務(runnable)的開始執行的順序.

但是這里new出來的線程池跟 Executors.newCachedThreadPool()方式創建出來的線程池, 在我看來沒有任何區別.
另外為什么要創建一個線程池來執行后臺cleanup工作? 真的有此必要嗎?
這個線程池對象只處理這么一個任務:cleanupRunnable 并且還只處理一次. why? 為什么要用線程池??

猜測可能的好處: 方便的創建后臺執行的線程, 設置線程名字;
ConnectionPool.put()是實例方法, 也就是說如果有多個地方創建了不同的ConnectionPool對象, 那么會有對應的多個cleanupRunnable任務對象, 都會添加到靜態實例executor線程池中.
這是一個多對1的關系.

終于明白用線程池的原因了, 對于每個連接池對象ConnectionPool 在某些時刻, cleanupRunnable是可以停止運行的, 比如長時間沒有連接了, 我還cleanup個毛了, 也就自己停止運行了:
具體代碼邏輯:
[圖片上傳中。。。(1)] ...

[圖片上傳中。。。(2)] 這個cleanup方法返回-1 標志這可以停止運行cleanupRunnable任務了; [圖片上傳中。。。(3)]
如上圖的return, 也就停止了運行. 但以后如果還有新的連接connection對象生成了, 那么這個任務又得重新新建, 并放到線程池中運行.
這么一停 一起 的操作 , 非常適合在線程池中實現.

這個ConnectionPool更像是一個cache, 因為創建新Connection的操作不在get()操作中.


緩存
跟glide這種圖片庫不同, okhttp的緩存是基于http協議標準的, 主要是通過header字段, 和返回碼來決定的, ---------這個地方需要確認, 好像也不是這么回事
所以, 重點是協議的邏輯. 準備買本http的書好好補充下基礎知識

----------------責任鏈模式
不同interceptor會有不同的處理方式, 可以選擇讓下一個intercepter先執行, 比如retryAndFollowUpInterceptor 只是靜觀其變, 它要等待其他人處理完之后在決定是否需要重試.
也可以選擇先把請求處理一番, 比如bridgeInterceptor, 會把原始請求封裝成一個包含http header的具體http請求.
也有的直接決定終止責任鏈的傳遞, 比如CacheInterceptor, 當它發現可以直接使用緩存數據時, 就直接返回緩存數據, 沒有必要繼續往下了, 因為往下可能就真的去聯網了.

為什么Dispatcher類中用了一堆Deque, ArrayDeque 為什么? 這比ArrayList有什么優勢嗎? 目前還想不明白.

--------------問題

  1. OkHttpClient對象是單例嗎? 那么ConnectionPool是單例嗎?
    可以自己封裝成單例. ConnectionPool對象與OkHttpClient對象是一對一的.

  2. StreamAllocation 與Connection 對象應該是多對一的關系, 一個connection可以被多個StreamAllocation對象引用. 而同時在 connection內部也有一個虛引用列表, 記錄了所有引用自己的StreamAllocation對象, 當這個列表為空時 , 說明是空閑的.

  3. 在緩存這塊對DiskLruCache的使用基本上就是用FileSystem替換了原始的java.io

如上圖的return, 也就停止了運行. 但以后如果還有新的連接connection對象生成了, 那么這個任務又得重新新建, 并放到線程池中運行.
這么一停 一起 的操作 , 非常適合在線程池中實現.

這個ConnectionPool更像是一個cache, 因為創建新Connection的操作不在get()操作中.


緩存
跟glide這種圖片庫不同, okhttp的緩存是基于http協議標準的, 主要是通過header字段, 和返回碼來決定的, ---------這個地方需要確認, 好像也不是這么回事
所以, 重點是協議的邏輯. 準備買本http的書好好補充下基礎知識

----------------責任鏈模式
不同interceptor會有不同的處理方式, 可以選擇讓下一個intercepter先執行, 比如retryAndFollowUpInterceptor 只是靜觀其變, 它要等待其他人處理完之后在決定是否需要重試.
也可以選擇先把請求處理一番, 比如bridgeInterceptor, 會把原始請求封裝成一個包含http header的具體http請求.
也有的直接決定終止責任鏈的傳遞, 比如CacheInterceptor, 當它發現可以直接使用緩存數據時, 就直接返回緩存數據, 沒有必要繼續往下了, 因為往下可能就真的去聯網了.


圖片.png

上圖284行這個位置, 很是疑問, 這種形式的for循環中就這么刪除一個元素, 然后還可能繼續循環, 繼續刪除, 這看起來是個錯誤啊. 但是這么有名的項目, 不可能犯這種錯誤, 但我就是想不明白為什么這么用/!!!!!

為什么Dispatcher類中用了一堆Deque, ArrayDeque 為什么? 這比ArrayList有什么優勢嗎? 目前還想不明白.

--------------問題

  1. OkHttpClient對象是單例嗎? 那么ConnectionPool是單例嗎?
    可以自己封裝成單例. ConnectionPool對象與OkHttpClient對象是一對一的.

  2. StreamAllocation 與Connection 對象應該是多對一的關系, 一個connection可以被多個StreamAllocation對象引用. 而同時在 connection內部也有一個虛引用列表, 記錄了所有引用自己的StreamAllocation對象, 當這個列表為空時 , 說明是空閑的.

  3. 在緩存這塊對DiskLruCache的使用基本上就是用FileSystem替換了原始的java.io

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容