1. HttpClient與HttpUrlConnection的區(qū)別
簡而言之:就是Volley的的請求方式(API2.3之前用的httpClient 2.3之后用的HttpUrlconnection)
httpclient 和httpUrlConnection都支持https協(xié)議, 都是以流的形式進行傳輸數(shù)據(jù).支持IPv6以及連接池等功能.
httpclient擁有很多API 保證它的兼容性進行拓展很難,google在6.0的時候廢棄Httpclient AS想用這個類可以使用:org.apach.http.legacy
HttpUrlConnection:輕量級,API較少,易拓展,滿足大部分的android數(shù)據(jù)傳輸 .例如Volley框架
2. OKHttp源碼分析
-
使用步驟: 首先創(chuàng)建OKhttpClient對象, 再創(chuàng)建一個 request請求 ,給request添加url,header請求方式等參數(shù), 用OKhttpclient.newCall方法返回一個call對象.同步方法: call.execute()來請求數(shù)據(jù),異步:call.euque();
-
dispatcher任務(wù)調(diào)度器,
- 它定義了三個雙向任務(wù)隊列,二個異步隊列:runningAsyncCalls和readyASyncCalls和一個運行在同步請求的runningSyncCalls隊列
- 一個標準線程池 executorServic 無界線程池,60s回收,用于大量耗時較短的異步任務(wù)
-
Request請求
Request的builder方法默認請求方式為GET
-
通過OkHttpclient和request構(gòu)造一個call對象,然后把這個call封裝到RealCall對象中
public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); } static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket){ // Safely publish the Call instance to the EventListener. RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; } private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); }
在realCall中構(gòu)造了一個RetryAndFollowUpInterceptor攔截器用于處理請求錯誤和重定向等,這個是Okhttp框架精髓 interceptor chain中的一環(huán), 默認情況下的第一個攔截器,除非調(diào)用OKhttpclient.builder.addInterceptor來添加全局攔截器,在RealCall.getResponseWithInterceptorChain()中添加默認的5個攔截器
-
RealCall
-
enque(callBack方法)
// RealCall.java public void enqueue(Callback responseCallback) { synchronized (this) { //每個請求只能之執(zhí)行一次 if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); client.dispatcher().enqueue(new AsyncCall(responseCallback)); }
-
一個call只能執(zhí)行一次,否則會出現(xiàn)異常,這里創(chuàng)建一個AsyncCall并將CallBack穿入,接著交給任務(wù)分發(fā)器Dispatcher來處理
// dispatcher.java synchronized void enqueue(AsyncCall call) { //正在執(zhí)行的任務(wù)數(shù)量小于最大值(64)往衷,并且此任務(wù)所屬主機的正在執(zhí)行任務(wù)小于最大值(5) if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } }
從dispatcher的enqueue方法看出,對于入隊做了限制,dispatcher類定義了請求數(shù)量的最大值為64個,請求的主機正再執(zhí)行任務(wù)小于5臺,滿足以上要求的線程就可以加入隊列,通過線程池執(zhí)行該任務(wù),否則加入readyAsyncCalls線程池中等待.
-
-
AsyncCall
-
它繼承于NameRunnable 而NameRunnable實現(xiàn)runnable接口. 作用1. 采用模板方法的設(shè)計模式,讓子類具體操作放在execute()中, 作用2. 給線程指定一個名稱
@Override protected void execute() { boolean signalledCallback = false; try { //調(diào)用 getResponseWithInterceptorChain()獲得響應(yīng)內(nèi)容 Response response = getResponseWithInterceptorChain(); if (retryAndFollowUpInterceptor.isCanceled()) { //這個標記為主要是避免異常時2次回調(diào) signalledCallback = true; //回調(diào)Callback告知失敗 responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; //回調(diào)Callback忠藤,將響應(yīng)內(nèi)容傳回去 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 { eventListener.callFailed(RealCall.this, e); responseCallback.onFailure(RealCall.this, e); } } finally { //不管請求成功與否咒彤,都進行finished()操作 client.dispatcher().finished(this); } }
-
client.dispatcher().finished(this)方法
private void promoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. //若條件允許,將readyAsyncCalls中的任務(wù)移動到runningAsyncCalls中烹骨,并交給線程池執(zhí)行 for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall call = i.next(); if (runningCallsForHost(call) < maxRequestsPerHost) { i.remove(); runningAsyncCalls.add(call); executorService().execute(call); } //當(dāng)runningAsyncCalls滿了熙掺,直接退出迭代 if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. } }
promoteCalls方法推動了下個任務(wù)的執(zhí)行,邏輯: 判斷正在請求的隊列是否大于64個,判斷正在準備的隊列是否為空,若條件滿足, readyAsyncCall的任務(wù)移動到runningAsyncCalls中,并且交給線程池去執(zhí)行
-
-
interceptorChain
-
重點就是interceptors這個集合,是將前面用戶自己創(chuàng)建的攔截器,OkHttp自帶的攔截器組合成一個攔截器鏈
錯誤重定向攔截器,(retryAndFollowUpInterceptor)
橋接攔截器 (BridgeInterceptor)
緩存攔截器(CacheInterceptor)
連接攔截器(ConnectInterceptor)
-
網(wǎng)絡(luò)攔截器 (networkInterceptors)
最后通過RealInterceptorChain#proceed(Request)來執(zhí)行interceptor chain
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); //這是一個List,是有序的 interceptors.addAll(client.interceptors());//首先添加的是用戶添加的全局攔截器 interceptors.add(retryAndFollowUpInterceptor); //錯誤伞辛、重定向攔截器 //橋接攔截器,橋接應(yīng)用層與網(wǎng)絡(luò)層夯缺,添加必要的頭蚤氏、 interceptors.add(new BridgeInterceptor(client.cookieJar())); //緩存處理,Last-Modified踊兜、ETag竿滨、DiskLruCache等 interceptors.add(new CacheInterceptor(client.internalCache())); //連接攔截器 interceptors.add(new ConnectInterceptor(client)); //從這就知道,通過okHttpClient.Builder#addNetworkInterceptor()傳進來的攔截器只對非網(wǎng)頁的請求生效 if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } //真正訪問服務(wù)器的攔截器 interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); return chain.proceed(originalRequest); }
-
RealInterceptorChain#proceed()
public Response proceed(Request request) throws IOException { return proceed(request, streamAllocation, httpCodec, connection); } public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, //...異常處理 // Call the next interceptor in the chain. RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next); //...異常處理 return response; } }
-
是按照interceptors集合的順序,逐個往下調(diào)用攔截器的intercept方法,所以最先的攔截器RetryAndFollowUpInterceptor 被調(diào)用
// RetryAndFollowUpInterceptor .java public Response intercept(Chain chain) throws IOException { Request request = chain.request(); RealInterceptorChain realChain = (RealInterceptorChain) chain; Call call = realChain.call(); EventListener eventListener = realChain.eventListener(); //創(chuàng)建一個StreamAllocation StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; //統(tǒng)計重定向次數(shù)捏境,不能大于20 int followUpCount = 0; Response priorResponse = null; while (true) { if (canceled) { streamAllocation.release(); throw new IOException("Canceled"); } Response response; boolean releaseConnection = true; try { //調(diào)用下一個interceptor的來獲得響應(yīng)內(nèi)容 response = realChain.proceed(request, streamAllocation, null, null); releaseConnection = false; } catch (RouteException e) { // The attempt to connect via a route failed. The request will not have been sent. if (!recover(e.getLastConnectException(), streamAllocation, false, request)) { throw e.getLastConnectException(); } releaseConnection = false; continue; } catch (IOException e) { // An attempt to communicate with a server failed. The request may have been sent. boolean requestSendStarted = !(e instanceof ConnectionShutdownException); if (!recover(e, streamAllocation, requestSendStarted, request)) throw e; releaseConnection = false; continue; } finally { // We're throwing an unchecked exception. Release any resources. if (releaseConnection) { streamAllocation.streamFailed(null); streamAllocation.release(); } } // Attach the prior response if it exists. Such responses never have a body. if (priorResponse != null) { response = response.newBuilder() .priorResponse(priorResponse.newBuilder() .body(null) .build()) .build(); } //重定向處理 Request followUp = followUpRequest(response, streamAllocation.route()); if (followUp == null) { if (!forWebSocket) { streamAllocation.release(); } return response; } closeQuietly(response.body()); if (++followUpCount > MAX_FOLLOW_UPS) { streamAllocation.release(); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } if (followUp.body() instanceof UnrepeatableRequestBody) { streamAllocation.release(); throw new HttpRetryException("Cannot retry streamed HTTP body", response.code()); } if (!sameConnection(response, followUp.url())) { streamAllocation.release(); streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(followUp.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; } else if (streamAllocation.codec() != null) { throw new IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?"); } request = followUp; priorResponse = response; } }
-
這個攔截器主要用于錯誤處理和重定向等問題,
總結(jié):
-
3. OKHttp架構(gòu)分析
-
異步請求線程池, Dispather
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; }
- 類似于線程池的newCacheThreadPool
- 無上限,60s自動回收線程,用于大量耗時較短的任務(wù)
- dispatcher提供修改最大異步任務(wù)數(shù)和最多主機的接口.默認是64個任務(wù)5臺主機
- 通過雙端隊列來維護準備執(zhí)行的任務(wù)和正在執(zhí)行的任務(wù),readyAsyncCalls,runningAsyncCalls
- 在每個任務(wù)結(jié)束后,都會檢查readyAsyncCalls是否有任務(wù),在滿足的情況下,按照先進先出的原則將任務(wù)移動到runningAsyncCalls隊列中,并在線程池中執(zhí)行
-
連接池清理線程池- ConnectionPool
該線程池是用來清理長時間閑置的和泄露的連接
該線程池本身無上限,閑置60s回收
-
雖然無限制,但是通過clearRunning標記來控制只有一個線程在運行,當(dāng)連接池中沒有連接時才會重新設(shè)置為fasle
void put(RealConnection connection) { assert (Thread.holdsLock(this)); if (!cleanupRunning) { cleanupRunning = true; executor.execute(cleanupRunnable); } connections.add(connection); }
工作線程會不斷地被清理,當(dāng)清理一遍完成后,根據(jù)線程池中的空閑超時連接計算出一個阻塞時間并阻塞,直到線程池中沒有任何連接才結(jié)束,并將clearRunning設(shè)置為fasle
-
每次有連接加入線程池中,如果當(dāng)前沒有清理任務(wù)運行,會加入一個清理任務(wù)到線程池中運行,
void put(RealConnection connection) { assert (Thread.holdsLock(this)); if (!cleanupRunning) { cleanupRunning = true; executor.execute(cleanupRunnable); } connections.add(connection); }
- 緩存整理線程池 DisLruCache
- 該線程池用于整理本地請求緩存數(shù)據(jù)
- 緩存的整理包含,達到閾值大小的文件,刪除最近最少使用的記錄,在有關(guān)操作達到一定數(shù)量以后對記錄進行重建
- 最大運行數(shù)量為1,無需考慮線程安全問題,自動回收閑置60s的線程
- Http2異步事務(wù)線程池, http2Connection
4. OKHTTP內(nèi)部緩存
-
使用場景: 數(shù)據(jù)更新不頻繁的查詢操作,客戶端緩存可以減少服務(wù)器的訪問次數(shù),無網(wǎng)絡(luò)時候也可以顯示歷史數(shù)據(jù),
max-age:這個參數(shù)告訴瀏覽器將頁面緩存多長時間于游,超過這個時間后才再次向服務(wù)器發(fā)起請求檢查頁面是否有更新。對于靜態(tài)的頁面垫言,比如圖片贰剥、CSS、Javascript筷频,一般都不大變更蚌成,因此通常我們將存儲這些內(nèi)容的時間設(shè)置為較長的時間,這樣瀏覽器會不會向瀏覽器反復(fù)發(fā)起請求凛捏,也不會去檢查是否更新了担忧。 s-maxage:這個參數(shù)告訴緩存服務(wù)器(proxy,如Squid)的緩存頁面的時間坯癣。如果不單獨指定涵妥,緩存服務(wù)器將使用max-age。對于動態(tài)內(nèi)容(比如文檔的查看頁面),我們可告訴瀏覽器很快就過時了(max-age=0)蓬网,并告訴緩存服務(wù)器(Squid)保留內(nèi)容一段時間(比如窒所,s-maxage=7200)。一旦我們更新文檔帆锋,我們將告訴Squid清除老的緩存版本吵取。 must-revalidate:這告訴瀏覽器,一旦緩存的內(nèi)容過期锯厢,一定要向服務(wù)器詢問是否有新版本皮官。 proxy-revalidate:proxy上的緩存一旦過期,一定要向服務(wù)器詢問是否有新版本实辑。 no-cache:不做緩存捺氢。 no-store:數(shù)據(jù)不在硬盤中臨時保存,這對需要保密的內(nèi)容比較重要剪撬。 public:告訴緩存服務(wù)器, 即便是對于不該緩存的內(nèi)容也緩存起來摄乒,比如當(dāng)用戶已經(jīng)認證的時候。所有的靜態(tài)內(nèi)容(圖片残黑、Javascript馍佑、CSS等)應(yīng)該是public的。 private:告訴proxy不要緩存梨水,但是瀏覽器可使用private cache進行緩存拭荤。一般登錄后的個性化頁面是private的。 no-transform: 告訴proxy不進行轉(zhuǎn)換疫诽,比如告訴手機瀏覽器不要下載某些圖片舅世。 max-stale指示客戶機可以接收超出超時期間的響應(yīng)消息。如果指定max-stale消息的值奇徒,那么客戶機可以接收超出超時期指定值之內(nèi)的響應(yīng)消息歇终。
-
okhttp的緩存設(shè)置 設(shè)置緩存的目錄
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); File cacheFile = new File(content.getExternalCacheDir(),"tangnuer"); Cache cache = new Cache(cacheFile,1024*1024*50); //使用 httpClientBuilder .cache(cache) .connectTimeout(20, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS)
-
源碼分析:
-
源碼入口CacheInterceptor#intercept
public Response intercept(Chain chain) throws IOException { //判斷是否設(shè)置cache Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse; //如果cache 不為null,從strategy中追蹤Response //主要是對networkRequest 或cacheResponse 進行計數(shù) if (cache != null) { cache.trackResponse(strategy); } //如果緩存不適用逼龟,則關(guān)閉IO流 if (cacheCandidate != null && cacheResponse == null) { closeQuietly(cacheCandidate.body()); } // 如果網(wǎng)絡(luò)被禁止并且無緩存评凝,則返回失敗504 if (networkRequest == null && cacheResponse == null) { return new Response.Builder() .request(chain.request()) .protocol(Protocol.HTTP_1_1) .code(504) .message("Unsatisfiable Request (only-if-cached)") .body(Util.EMPTY_RESPONSE) .sentRequestAtMillis(-1L) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); } // 在無網(wǎng)絡(luò)時從緩存中獲取 if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } Response networkResponse = null; try { //調(diào)用下一個攔截器,訪問網(wǎng)絡(luò) networkResponse = chain.proceed(networkRequest); } finally { // If we're crashing on I/O or otherwise, don't leak the cache body. if (networkResponse == null && cacheCandidate != null) { closeQuietly(cacheCandidate.body()); } } // 如果緩存中已經(jīng)存在對應(yīng)的Response的處理 if (cacheResponse != null) { //表示數(shù)據(jù)未做修改 if (networkResponse.code() == HTTP_NOT_MODIFIED) { Response response = cacheResponse.newBuilder() .headers(combine(cacheResponse.headers(), networkResponse.headers())) .sentRequestAtMillis(networkResponse.sentRequestAtMillis()) .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); networkResponse.body().close(); //主要是更新response頭部數(shù)據(jù) cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); //寫入緩存 if (cache != null) { if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { // Offer this request to the cache. CacheRequest cacheRequest = cache.put(response); return cacheWritingResponse(cacheRequest, response); } //移除networkRequest if (HttpMethod.invalidatesCache(networkRequest.method())) { try { cache.remove(networkRequest); } catch (IOException ignored) { // The cache cannot be written. } } } return response; }
- CacheStrategy內(nèi)部分裝了網(wǎng)絡(luò)請求對象newWorkRequest和CacheResponse(開始獲取的候選緩存對象CacheCandie),它是Okhttp的緩存策略核心.
- 回到代碼, 首先判斷是否已設(shè)置了cache,如果已經(jīng)設(shè)置,根據(jù)chain.request()返回的request查找cache中對應(yīng)的response對象,然后創(chuàng)建一個CacheStrategy對象strategy,
- 再通過對網(wǎng)絡(luò)狀態(tài)的判斷和緩存狀態(tài)的判斷,如果是網(wǎng)絡(luò)獲取未緩存的,得到response對象后,會更新寫入到緩存中,再返回, 而上述代碼就是對cache的操作(get,put,update,remove)
-
Cache的產(chǎn)生
cache是internalCache類型的對象,internalCache是okhttp的內(nèi)部緩存接口,
-
Cache類的構(gòu)造方法
public Cache(File directory, long maxSize) { this(directory, maxSize, FileSystem.SYSTEM); } Cache(File directory, long maxSize, FileSystem fileSystem) { this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize); }
創(chuàng)建了DiskLruCache類型的cache實例,這里的FileSystem.SYSTEM是FileSystem的實現(xiàn),內(nèi)部是基于OKio的singk/source對緩存文件進行流的操作,DiskLRUCache.entry內(nèi)部維護了二個數(shù)組,保存每個Url請求對應(yīng)文件的引用.然后通過DiskLruCache.editor操作數(shù)組,并為Cache.entry提供Sink/source,對文件流進行操作,
-
-
Cache的操作
-
put操作
CacheRequest put(Response response) { //獲取請求方法 String requestMethod = response.request().method(); if (HttpMethod.invalidatesCache(response.request().method())) { try { remove(response.request()); } catch (IOException ignored) { // The cache cannot be written. } return null; } //如果不是GET請求時返回的response腺律,則不進行緩存 if (!requestMethod.equals("GET")) { return null; } if (HttpHeaders.hasVaryAll(response)) { return null; } //把response封裝在Cache.Entry中奕短,調(diào)用DiskLruCache的edit()返回editor Entry entry = new Entry(response); DiskLruCache.Editor editor = null; try { //把url進行 md5()湘今,并轉(zhuǎn)換成十六進制格式 //將轉(zhuǎn)換后的key作為DiskLruCache內(nèi)部LinkHashMap的鍵值 editor = cache.edit(key(response.request().url())); if (editor == null) { return null; } //用editor提供的Okio的sink對文件進行寫入 entry.writeTo(editor); //利用CacheRequestImpl寫入body return new CacheRequestImpl(editor); } catch (IOException e) { abortQuietly(editor); return null; } }
OkHttp只針對Get請求時返回的數(shù)據(jù)進行緩存,
官方解釋:非Get請求返回的Response也可以進行緩存,但是這樣做復(fù)雜性搞高,且效益低
DiskLruCache.editor獲取editor對象后,調(diào)用writeTo吧url,請求方法,響應(yīng)頭部字段等寫入緩存,返回一個CacheRequestImpl實例,public @Nullable Editor edit(String key) throws IOException { return edit(key, ANY_SEQUENCE_NUMBER); } synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { //內(nèi)部主要是利用FileSystem處理文件瀑焦,如果這里出現(xiàn)了異常, //在最后會構(gòu)建新的日志文件丧枪,如果文件已存在之斯,則替換 initialize(); //檢測緩存是否已關(guān)閉 checkNotClosed(); //檢測是否為有效key validateKey(key); //lruEntries是LinkHashMap的實例日杈,先查找lruEntries是否存在 Entry entry = lruEntries.get(key); if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) { return null; // Snapshot is stale. } //如果有Editor在操作entry,返回null if (entry != null && entry.currentEditor != null) { return null; } //如果需要,進行clean操作 if (mostRecentTrimFailed || mostRecentRebuildFailed) { executor.execute(cleanupRunnable); return null; } // 把當(dāng)前key在對應(yīng)文件中標記DIRTY狀態(tài)莉擒,表示正在修改酿炸, //清空日志緩沖區(qū),防止泄露 journalWriter.writeUtf8(DIRTY).writeByte(' ').writeUtf8(key).writeByte('\n'); journalWriter.flush(); if (hasJournalErrors) { return null; // 如果日志文件不能編輯 } //為請求的url創(chuàng)建一個新的DiskLruCache.Entry實例 //并放入lruEntries中 if (entry == null) { entry = new Entry(key); lruEntries.put(key, entry); } Editor editor = new Editor(entry); entry.currentEditor = editor; return editor; }
最后的到DiskLruCache.Entry實例,這個實例主要維護Key對應(yīng)的文件列表,并且內(nèi)部currentEditor不為null,表示當(dāng)前實例處于編譯狀態(tài),返回得到editor后,調(diào)用Cache.Entry的writeTo對editor進行操作
public void writeTo(DiskLruCache.Editor editor) throws IOException { BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA)); sink.writeUtf8(url) .writeByte('\n'); sink.writeUtf8(requestMethod) .writeByte('\n'); sink.writeDecimalLong(varyHeaders.size()) .writeByte('\n'); //... ...省略涨冀,都是利用sink進行寫入操作 sink.close(); }
editor.newSink為上層Cache.entry提供一個sink,然后進行文件寫入操作,這里只是寫入url,請求方法,頭部字段等,并未寫入body部分,body部分的寫入在CacheInterceptor.intercept方法的內(nèi)部調(diào)用CacheWritingResponse寫入body
@Override public Response intercept(Chain chain) throws IOException { ... ... //省略代碼 if (cache != null) { if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { // Offer this request to the cache. CacheRequest cacheRequest = cache.put(response); //在內(nèi)部實現(xiàn)了response的body的寫入 return cacheWritingResponse(cacheRequest, response); } ... ... //省略代碼 )
-
get操作
@Nullable Response get(Request request) { //把url轉(zhuǎn)換成key String key = key(request.url()); DiskLruCache.Snapshot snapshot; Entry entry; try { //通過DiskLruCache的get()根據(jù)具體的key獲取DiskLruCache.Snapshot實例 snapshot = cache.get(key); if (snapshot == null) { return null; } } catch (IOException e) { // Give up because the cache cannot be read. return null; } try { //通過snapshot.getSource()獲取一個Okio的Source entry = new Entry(snapshot.getSource(ENTRY_METADATA)); } catch (IOException e) { Util.closeQuietly(snapshot); return null; } //根據(jù)snapshot獲取緩存中的response Response response = entry.response(snapshot); if (!entry.matches(request, response)) { Util.closeQuietly(response.body()); return null; } return response; }
snapshor是DiskLruCache.Entry的一個快照,內(nèi)部封裝了DiskLruCache.entry對應(yīng)文件的source,簡單來說,根據(jù)條件從DiskLruCache.entry找到對應(yīng)的緩存文件,并生成source文件,封裝在snapshot內(nèi)部,通過snapshort.getsource獲取source,對文件進行讀取操作
//DiskLruCache # get() public synchronized Snapshot get(String key) throws IOException { initialize(); checkNotClosed(); validateKey(key); //從lruEntries查找entry填硕, Entry entry = lruEntries.get(key); if (entry == null || !entry.readable) return null; //得到Entry的快照值snapshot Snapshot snapshot = entry.snapshot(); if (snapshot == null) return null; redundantOpCount++; journalWriter.writeUtf8(READ).writeByte(' ').writeUtf8(key).writeByte('\n'); //如果redundantOpCount超過2000,且超過lruEntries的大小時鹿鳖,進行清理操作 if (journalRebuildRequired()) { executor.execute(cleanupRunnable); } return snapshot; } //DiskLruCache.Entry # snapshot() Snapshot snapshot() { if (!Thread.holdsLock(DiskLruCache.this)) throw new AssertionError(); Source[] sources = new Source[valueCount]; // Defensive copy since these can be zeroed out. long[] lengths = this.lengths.clone(); try { //遍歷已緩存的文件扁眯,生成相應(yīng)的sources for (int i = 0; i < valueCount; i++) { sources[i] = fileSystem.source(cleanFiles[i]); } //創(chuàng)建Snapshot并返回 return new Snapshot(key, sequenceNumber, sources, lengths); } catch (FileNotFoundException e) { // A file must have been deleted manually! for (int i = 0; i < valueCount; i++) { if (sources[i] != null) { Util.closeQuietly(sources[i]); } else { break; } } // Since the entry is no longer valid, remove it so the metadata is accurate (i.e. the cache // size.) try { removeEntry(this); } catch (IOException ignored) { } return null; } }
Cache.Entry # response()
public Response response(DiskLruCache.Snapshot snapshot) { String contentType = responseHeaders.get("Content-Type"); String contentLength = responseHeaders.get("Content-Length"); Request cacheRequest = new Request.Builder() .url(url) .method(requestMethod, null) .headers(varyHeaders) .build(); return new Response.Builder() .request(cacheRequest) .protocol(protocol) .code(code) .message(message) .headers(responseHeaders) .body(new CacheResponseBody(snapshot, contentType, contentLength)) .handshake(handshake) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(receivedResponseMillis) .build(); }
-
-
總結(jié) : Cache只是一個上層的執(zhí)行者,內(nèi)部真正的緩存是由DiskLruCache實現(xiàn)的,在DiskLruCache里面通過FileStem,基于okio的sink/source對文件進行流的操作.
福利
??由于篇幅有限很多細節(jié)無法具體分析 如想了解更多OkHttp有關(guān)的知識, 我推薦一部詳細的開源框架視頻教程 視頻地址在youtub上 當(dāng)然本人也是小白一個 只能做資源的搬運..