在OkHttp框架基础上打造客户端可控的缓存框架,带你彻底理解OkHttp的缓存功能模块

话说okhttp已经具备强大的缓存功能,我为什么还要改造它呢?虽然okhttp的缓存功能很强大,但是缓存功能是依赖响应头而设计的,如果服务端不给你提供响应头的话,那么这个框架默认是不会保存你请求的数据的,也就是说如果我在app端显示数据,想在网络差的情况下、或网络断掉的情况下能让界面快速显示从而提高用户体验的话,添加缓存是必不可少的,就像浏览器一样已经浏览了网页的话,第二次打开网页会很快,但是只是写上缓存的话,会存在一个问题(这个缓存什么时候该更新,就是客户端什么时候应该去服务器重新请求数据),打个比方你写了一个购物车界面,第一次请求网络的时候,你将购物车里面的数据缓存到本地,以后请求的话,从缓存里面直接取,即省了流量,也提高了响应速度,但是这时你向购物车又增加了一个产品的话,刷新购物车的时候你觉着还是从缓存当中取吗?当然不行,数据必须和服务端数据保持一致才行,这个时候你就需要知道什么时候应该重新从服务端获取数据,这就需要为每一个缓存文件对应一个有效时间,如果文件有效直接获得缓存,如果无效则从从服务器重新获得,并将原来的缓存文件覆盖。

 而这个过期时间设置多长合适呢?时间太长的话服务端数据和客户端数据不一样的概率会大大增加,时间短的话,这个缓存又失去了意义,既然每次都重新到服务端获取了,我要你缓存有什么用,还多那么多的代码。那么过期时间由谁来指定比较好呢?答案当然是服务端,而okhttp框架恰好就是利用了这个机制(通过请求头来控制缓存时间)。问题又来了,如果服务端的开发人员配合你这么写,那么将省很多事,但是,如果你遇到奇葩的服务端这么办?服务端说:我就不给你搞?你能咋地?,这时是不是有想踹他两脚的冲动,他不愿搞,只有两个方面:1、是真的不想给你搞。2、原来代码耦合性太大,改起来麻烦。3、不会搞。

笔者就遇到了这么操蛋的搞后台的,既然他不搞,咱客户端也不搞吧,大家都省事。但是作为一个有素养的程序员,咱得对咱自己的项目负责不是。既然你后台不配合搞,我又没服务器密码,没法自己写后台代码,那么只有改造自己的代码了。秉着负责的态度,开搞。

在改造之前,咱们先来熟悉下okhttp缓存模块流程,原理、流程知道了,改起来才得心应手,okhttp所有请求的发起都是通过HttpEngine这个类,至于请求怎么发起的源头,这里不做过多讨论。

既然带有缓存的话,那么缓存的获取肯定是发生在建立连接之前的,发送请求是HttpEngine的sendRequest方法

public void sendRequest() throws RequestException, RouteException,
			IOException {
		if (cacheStrategy != null)
			return; // Already sent.
		if (transport != null)
			throw new IllegalStateException();
		// 重新装配请求
		Request request = networkRequest(userRequest);
        //得到缓存操作类
		InternalCache responseCache = Internal.instance.internalCache(client);
		//通过url获取缓存映射为Response
		Response cacheCandidate = responseCache != null ? responseCache
				.get(request) : null;

		long now = System.currentTimeMillis();
		//缓存策略类定义(其中判断了缓存是否过期等等)
		cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate)
				.get();
		//如果缓存有效的话,则networkRequest将为null
		networkRequest = cacheStrategy.networkRequest;
		cacheResponse = cacheStrategy.cacheResponse;
        //修改缓存命中的次数,如果有缓存的话
		if (responseCache != null) {
			responseCache.trackResponse(cacheStrategy);
		}
		//假如确定不采用缓存的话
		if (cacheCandidate != null && cacheResponse == null) {
			//关闭缓存的管道流
			closeQuietly(cacheCandidate.body()); // The cache candidate wasn't
													// applicable. Close it.
		}
        /**
     * 建立网络连接
     */
		if (networkRequest != null) {
			// Open a connection unless we inherited one from a redirect.
			if (connection == null) {
				connect();
			}

			transport = Internal.instance.newTransport(connection, this);

			// If the caller's control flow writes the request body, we need to
			// create that stream
			// immediately. And that means we need to immediately write the
			// request headers, so we can
			// start streaming the request body. (We may already have a request
			// body if we're retrying a
			// failed POST.)
			// permitsRequestBody(networkRequest)判断请求方式是否属于http的那些请求方式
			// callerWritesRequestBody默认为false
			if (callerWritesRequestBody && permitsRequestBody(networkRequest)
					&& requestBodyOut == null) {
				long contentLength = OkHeaders.contentLength(request);
				if (bufferRequestBody) {
					if (contentLength > Integer.MAX_VALUE) {
						throw new IllegalStateException(
								"Use setFixedLengthStreamingMode() or "
										+ "setChunkedStreamingMode() for requests larger than 2 GiB.");
					}

					if (contentLength != -1) {
						// Buffer a request body of a known length.
						transport.writeRequestHeaders(networkRequest);
						requestBodyOut = new RetryableSink((int) contentLength);
					} else {
						// Buffer a request body of an unknown length. Don't
						// write request
						// headers until the entire body is ready; otherwise we
						// can't set the
						// Content-Length header correctly.
						requestBodyOut = new RetryableSink();
					}
				} else {
					transport.writeRequestHeaders(networkRequest);
					requestBodyOut = transport.createRequestBody(
							networkRequest, contentLength);
				}
			}

		} else {
			// We aren't using the network. Recycle a connection we may have
			// inherited from a redirect.
			if (connection != null) {
				Internal.instance.recycle(client.getConnectionPool(),
						connection);
				connection = null;
			}

			if (cacheResponse != null) {
				// We have a valid cached response. Promote it to the user
				// response immediately.
				this.userResponse = cacheResponse.newBuilder()
						.request(userRequest)
						.priorResponse(stripBody(priorResponse))
						.cacheResponse(stripBody(cacheResponse)).build();
			} else {
				// We're forbidden from using the network, and the cache is
				// insufficient.
				this.userResponse = new Response.Builder().request(userRequest)
						.priorResponse(stripBody(priorResponse))
						.protocol(Protocol.HTTP_1_1).code(504)
						.message("Unsatisfiable Request (only-if-cached)")
						.body(EMPTY_BODY).build();
			}
			// zip解压缩
			userResponse = unzip(userResponse);
		}
	}

这个方法和缓存有关的就这么多,如下:

//得到缓存操作类
		InternalCache responseCache = Internal.instance.internalCache(client);
		//通过url获取缓存映射为Response
		Response cacheCandidate = responseCache != null ? responseCache
				.get(request) : null;

		long now = System.currentTimeMillis();
		//缓存策略类定义(其中判断了缓存是否过期等等)
		cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate)
				.get();
		//如果缓存有效的话,则networkRequest将为null
		networkRequest = cacheStrategy.networkRequest;
		cacheResponse = cacheStrategy.cacheResponse;
        //修改缓存命中的次数,如果有缓存的话
		if (responseCache != null) {
			responseCache.trackResponse(cacheStrategy);
		}
		//假如确定不采用缓存的话
		if (cacheCandidate != null && cacheResponse == null) {
			//关闭缓存的管道流
			closeQuietly(cacheCandidate.body()); // The cache candidate wasn't
													// applicable. Close it.
		}

InternalCache responseCache = Internal.instance.internalCache(client)这方法是获取缓存的操作类,它只在你设置了缓存的路径后才会有效,如果你不设置的话,这个将返回null。所以想用okhttp的缓存的话,你必须先设置缓存的路径,设置缓存路径通过OkHttpClientsetCache()方法设置,然后就是通过InternalCache 的get(request)方法获取缓存,如果本地存在缓存的话,则获得这个缓存文件,并通过response建立和缓存文件的通道,方法如下:

public Response get(Request request) {
		//将url进行md5值转化返回
		String key = urlToKey(request);
		//缓存信息的辅助类
		DiskLruCache.Snapshot snapshot;
		Entry entry;
		try {
			//看集合中有没有
			//snapshot中保存的头信息
			snapshot = cache.get(key);
			if (snapshot == null) {
				return null;
			}
		} catch (IOException e) {
			// Give up because the cache cannot be read.
			return null;
		}

		try {
			//Entry获取body信息的辅助类(这里的body就是json数据)
			entry = new Entry(snapshot.getSource(ENTRY_METADATA));
		} catch (IOException e) {
			Util.closeQuietly(snapshot);
			return null;
		}
        //通过头信息里面的长度找到body信息
		Response response = entry.response(request, snapshot);
       //再次判断请求的url和缓存的url是否匹配
		if (!entry.matches(request, response)) {
			Util.closeQuietly(response.body());
			return null;
		}

		return response;
	}

可以看出okhttp的缓存的操作完全是依赖于DiskLruCache这个缓存框架的,DiskLruCache缓存框架这里不再做详细探讨。这里假设本地存在缓存,也就cacheCandidate不为null,那么继续走到这cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get(),通过缓存策略类类操作缓存,也就是说缓存策略类是用来判断缓存是否可用的(缓存没有超过预设的时间,没有必要在服务器上重新获得),那么缓存获取的主要工作全是由这个策略类决定。来看看它的具体实现,创建这个策略类的时候先通过读取的缓存获得服务器返回的缓存头信息。

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

			if (cacheResponse != null) {
				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 = HeaderParser.parseSeconds(value, -1);
					} else if (OkHeaders.SENT_MILLIS
							.equalsIgnoreCase(fieldName)) {
						sentRequestMillis = Long.parseLong(value);
					} else if (OkHeaders.RECEIVED_MILLIS
							.equalsIgnoreCase(fieldName)) {
						receivedResponseMillis = Long.parseLong(value);
					}
				}
			}
		}

Date、Age、ETag、Last-Modified等这些都是http的响应缓存头信息,浏览器通过他们来实现缓存网页的策略.下面简单介绍一下:

对 于 Cache-Control 头里的 Public、Private、no-cache、max-age 、no-store 他们都是用来指明响应内容是否可以被客户端存储的,其中前4个都会缓存文件数据(关于 no-cache 应理解为“不建议使用本地缓存”,其仍然会缓存数据到本地),后者 no-store 则不会在客户端缓存任何响应数据。另关于 no-cache 和 max-age 有点特别,我认为它是一种混合体,下面我会讲到。

通 过 Cache-Control:Public 设置我们可以将 Http 响应数据存储到本地,但此时并不意味着后续浏览器会直接从缓存中读取数据并使用,为啥?因为它无法确定本地缓存的数据是否可用(可能已经失效),还必须借 助一套鉴别机制来确认才行, 这就是我们下面要讲到的“缓存过期策略”。Cache-Control 中指定的缓存过期策略优先级高于 Expires,当它们同时存在的时候,后者会被覆盖掉。缓存数据标记为已过期只是告诉客户端不能再直接从本地读取缓存了,需要再发一次请求到服务器去确认,并不等同于本地缓存数据从此就没用了,有些情况下即使过期了还是会被再次用到。

客 户端检测到数据过期或浏览器刷新后,往往会重新发起一个 http 请求到服务器,服务器此时并不急于返回数据,而是看请求头有没有带标识( If-Modified-Since、If-None-Match)过来,如果判断标识仍然有效,则返回304告诉客户端取本地缓存数据来用即可(这里要 注意的是你必须要在首次响应时输出相应的头信息(Last-Modified、ETags)到客户端)。至此我们就明白了上面所说的本地缓存数据即使被认 为过期,并不等于数据从此就没用了的道理了。

好了,我们继续走源码,缓存策略工厂类初始化之后就是创建缓存策略类了,也就是通过get()方法获取,如下:

public CacheStrategy get() {
			CacheStrategy candidate = getCandidate();
           //再获取缓存的话,可以通过控制请求Request来强制获取缓存,并把请求制为null
			if (candidate.networkRequest != null
					&& request.cacheControl().onlyIfCached()) {
				// We're forbidden from using the network and the cache is
				// insufficient.
				return new CacheStrategy(null, cacheResponse);
			}

			return candidate;
		}

这个方法可以看出在请求Request中可以通过设置CacheControl的类的属性来强制加载缓存,强制加载缓存的时候,networkRequest将制为null,就没有连接服务器的必要了,从而senRequest()方法连接服务器的代码将不再执行。继续来看getCandidate()方法,如下:

private CacheStrategy getCandidate() {
			// No cached response.
			if (cacheResponse == null) {
				return new CacheStrategy(request, null);
			}

			// Drop the cached response if it's missing a required handshake.
			//如果是https格式的还要保存握手信息,所以这里判断握手是否存在
			if (request.isHttps() && cacheResponse.handshake() == null) {
				return new CacheStrategy(request, null);
			}

			
			//request.isStore()自己添加
			//isCacheable方法判断只要request或者Response设置了CacheControl属性noStore为true则不采用缓存
			if (!isCacheable(cacheResponse, request)&&!request.isStore()) {
				return new CacheStrategy(request, null);
			}

			CacheControl requestCaching = request.cacheControl();
			if (requestCaching.noCache() || hasConditions(request)) {
				return new CacheStrategy(request, null);
			}
			// 接下来判断缓存是否过期
			long ageMillis = cacheResponseAge();
			//多长时间需要刷新,服务端返回的时间
			long freshMillis = computeFreshnessLifetime();

			if (requestCaching.maxAgeSeconds() != -1) {
				//这个是自己加的,如果客户端加入设置isStore为true的话
				if(request.isStore()){
					//刷新时间为
					freshMillis=SECONDS.toMillis(requestCaching.maxAgeSeconds());
				}else{
					//刷新时间为最大生命时间取最小
				freshMillis = Math.min(freshMillis,
						SECONDS.toMillis(requestCaching.maxAgeSeconds()));
				}
			}

			long minFreshMillis = 0;
			if (requestCaching.minFreshSeconds() != -1) {
				minFreshMillis = SECONDS.toMillis(requestCaching
						.minFreshSeconds());
			}

			long maxStaleMillis = 0;
			CacheControl responseCaching = cacheResponse.cacheControl();
			if (!responseCaching.mustRevalidate()
					&& requestCaching.maxStaleSeconds() != -1) {
				maxStaleMillis = SECONDS.toMillis(requestCaching
						.maxStaleSeconds());
			}
         /**
          * 如果有自己的设置request.isStore()忽略服务端返回的no-cache
          */
			if ((!responseCaching.noCache()
					&& ageMillis + minFreshMillis < freshMillis
							+ maxStaleMillis)||(request.isStore()&&ageMillis + minFreshMillis < freshMillis
							+ maxStaleMillis)) {
				Response.Builder builder = cacheResponse.newBuilder();
				if (ageMillis + minFreshMillis >= freshMillis) {
					builder.addHeader("Warning",
							"110 HttpURLConnection \"Response is stale\"");
				}
				long oneDayMillis = 24 * 60 * 60 * 1000L;
				if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
					builder.addHeader("Warning",
							"113 HttpURLConnection \"Heuristic expiration\"");
				}
				return new CacheStrategy(null, builder.build());
			}

			Request.Builder conditionalRequestBuilder = request.newBuilder();
            //新加请求头交给服务器判断文件是否修改
			if (etag != null) {
				conditionalRequestBuilder.header("If-None-Match", etag);
			} else if (lastModified != null) {
				conditionalRequestBuilder.header("If-Modified-Since",
						lastModifiedString);
			} else if (servedDate != null) {
				conditionalRequestBuilder.header("If-Modified-Since",
						servedDateString);
			}

			Request conditionalRequest = conditionalRequestBuilder.build();
			return hasConditions(conditionalRequest) ? new CacheStrategy(
					conditionalRequest, cacheResponse) : new CacheStrategy(
					conditionalRequest, null);
		}
代码虽然多但意思很好理解,这个方法的含义通俗的说就是缓存的保存日期和当前时间的差值有么有超过服务器设定的缓存时间,如果超过则认为现在的缓存是无效的,需要重新从网络中获取,这里注意我们可以通过 request的CacheControl的nostore来强制吧不使用缓存.如果缓存不满足条件,那么将为请求根据条件添加头信息,用来给服务端判断是否过期。
 //新加请求头交给服务器判断文件是否修改
			if (etag != null) {
				conditionalRequestBuilder.header("If-None-Match", etag);
			} else if (lastModified != null) {
				conditionalRequestBuilder.header("If-Modified-Since",
						lastModifiedString);
			} else if (servedDate != null) {
				conditionalRequestBuilder.header("If-Modified-Since",
						servedDateString);
			}

这就是整个缓存的获取过程了,接下来看一下缓存的存储,存储的方法在HttpEngine的readResponse()方法中,如下:

public void readResponse() throws IOException {
		if (userResponse != null) {
			return; // Already ready.
		}
		if (networkRequest == null && cacheResponse == null) {
			throw new IllegalStateException("call sendRequest() first!");
		}
		if (networkRequest == null) {
			return; // No network response to read.
		}

		Response networkResponse;

		if (forWebSocket) {
			transport.writeRequestHeaders(networkRequest);
			networkResponse = readNetworkResponse();

		} else if (!callerWritesRequestBody) {
			networkResponse = new NetworkInterceptorChain(0, networkRequest)
					.proceed(networkRequest);

		} else {
			// Emit the request body's buffer so that everything is in
			// requestBodyOut.
			if (bufferedRequestBody != null
					&& bufferedRequestBody.buffer().size() > 0) {
				bufferedRequestBody.emit();
			}

			// Emit the request headers if we haven't yet. We might have just
			// learned the Content-Length.
			if (sentRequestMillis == -1) {
				if (OkHeaders.contentLength(networkRequest) == -1
						&& requestBodyOut instanceof RetryableSink) {
					long contentLength = ((RetryableSink) requestBodyOut)
							.contentLength();
					networkRequest = networkRequest
							.newBuilder()
							.header("Content-Length",
									Long.toString(contentLength)).build();
				}
				transport.writeRequestHeaders(networkRequest);
			}

			// Write the request body to the socket.
			if (requestBodyOut != null) {
				if (bufferedRequestBody != null) {
					// This also closes the wrapped requestBodyOut.
					bufferedRequestBody.close();
				} else {
					requestBodyOut.close();
				}
				if (requestBodyOut instanceof RetryableSink) {
					transport.writeRequestBody((RetryableSink) requestBodyOut);
				}
			}

			networkResponse = readNetworkResponse();
		}
		/**
		 * cookie持久化
		 */
		receiveHeaders(networkResponse.headers());

		// If we have a cache response too, then we're doing a conditional get.
		if (cacheResponse != null) {
			// 验证网络获取和缓存获取是不是数据时效性一直
			if (validate(cacheResponse, networkResponse)) {
				userResponse = cacheResponse
						.newBuilder()
						.request(userRequest)
						.priorResponse(stripBody(priorResponse))
						.headers(
								combine(cacheResponse.headers(),
										networkResponse.headers()))
						.cacheResponse(stripBody(cacheResponse))
						.networkResponse(stripBody(networkResponse)).build();
				networkResponse.body().close();
				releaseConnection();

				// Update the cache after combining headers but before stripping
				// the
				// Content-Encoding header (as performed by
				// initContentStream()).
				InternalCache responseCache = Internal.instance
						.internalCache(client);
				responseCache.trackConditionalCacheHit();
				responseCache.update(cacheResponse, stripBody(userResponse));
				userResponse = unzip(userResponse);
				return;
			} else {
				closeQuietly(cacheResponse.body());
			}
		}

		userResponse = networkResponse.newBuilder().request(userRequest)
				.priorResponse(stripBody(priorResponse))
				.cacheResponse(stripBody(cacheResponse))
				.networkResponse(stripBody(networkResponse)).build();

		if (hasBody(userResponse)) {
			maybeCache();
			userResponse = unzip(cacheWritingResponse(storeRequest,
					userResponse));
		}
	}

和缓存有关的只有这么几句

if (hasBody(userResponse)) {
			maybeCache();
			userResponse = unzip(cacheWritingResponse(storeRequest,
					userResponse));
		}

其中maybeCache用来保存响应的状态信息和头信息等等,cacheWritingResponse方法用来储存服务器返回的消息体。来详细的看一下这两个方法:

	private void maybeCache() throws IOException {
		InternalCache responseCache = Internal.instance.internalCache(client);
		if (responseCache == null)
			return;

		// Should we cache this response for this request?
		if (!CacheStrategy.isCacheable(userResponse, networkRequest)&&!networkRequest.isStore()) {
			if (HttpMethod.invalidatesCache(networkRequest.method())) {
				try {
					responseCache.remove(networkRequest);
				} catch (IOException ignored) {
					// The cache cannot be written.
				}
			}
			return;
		}
      if(networkRequest.getReQuestCache()!=null&&!networkRequest.getReQuestCache().isRequestCache(userResponse))return;
		// Offer this request to the cache.
		storeRequest = responseCache.put(stripBody(userResponse));
	}

方法很简单,就是将服务器响应的信息文件保存一份,集合中保存获取它的对象,也就是Entry这个类,不过保存的时候这里注意一下,okhttp框架默认只缓存get方式的请求数据,如果你是post方式,不改动这部分源码的话将不会缓存数据。如下判断:

	}
		if (!requestMethod.equals("GET")) {
			// Don't cache non-GET responses. We're technically allowed to cache
			// HEAD requests and some POST requests, but the complexity of doing
			// so is high and the benefit is low.
			return null;
		}

最终通过下面这个方法将消息体保存到文件中,一般现在我们采用的都是json数据交互,所以消息实体也就是json的字符串保存到文件中

private Response cacheWritingResponse(final CacheRequest cacheRequest,
			Response response) throws IOException {
		// Some apps return a null body; for compatibility we treat that like a
		// null cache request.
		if (cacheRequest == null)
			return response;
		Sink cacheBodyUnbuffered = cacheRequest.body();
		if (cacheBodyUnbuffered == null)
			return response;
		if(response.body().getResult()!=null){
			 BufferedSink cacheBody = Okio.buffer(cacheBodyUnbuffered);
			 cacheBody.writeUtf8(response.body().getResult());
			 cacheBody.flush();
			 cacheBody.close();
			return response;
		} 
		final BufferedSource source = response.body().source();
		final BufferedSink cacheBody = Okio.buffer(cacheBodyUnbuffered);
		Source cacheWritingSource = new Source() {
			boolean cacheRequestClosed;

			@Override
			public long read(Buffer sink, long byteCount) throws IOException {
				long bytesRead;
				try {
					bytesRead = source.read(sink, byteCount);
				} catch (IOException e) {
					if (!cacheRequestClosed) {
						cacheRequestClosed = true;
						cacheRequest.abort(); // Failed to write a complete
												// cache response.
					}
					throw e;
				}

				if (bytesRead == -1) {
					if (!cacheRequestClosed) {
						cacheRequestClosed = true;
						cacheBody.close(); // The cache response is complete!
					}
					return -1;
				}

				sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead,
						bytesRead);
				cacheBody.emitCompleteSegments();
				return bytesRead;
			}

			@Override
			public Timeout timeout() {
				return source.timeout();
			}

			@Override
			public void close() throws IOException {
				if (!cacheRequestClosed
						&& !Util.discard(this,
								Transport.DISCARD_STREAM_TIMEOUT_MILLIS,
								MILLISECONDS)) {
					cacheRequestClosed = true;
					cacheRequest.abort();
				}
				source.close();
			}
		};

		return response
				.newBuilder()
				.body(new RealResponseBody(response.headers(), Okio
						.buffer(cacheWritingSource))).build();
	}
那么当这里保存数据的步骤就结束了。源码看到这里,如果我们想不通过服务端的请求头就实现缓存的话,首先你得保证你的请求是get方式,然后保证response的CacheControl的noStore不为true,这个字段哪来的,当然是服务器响应头里面,我得保证

Cache-Control响应头不返回no-store ,但是这又得依赖服务端,所以这部分不可避免的话,只有通过为Request添加属性绕过这个判断。这里就需要改造缓存策略类CacheStrategygetCandidate方法,首先在Request中添加属性

 //是否缓存内容交给客户端处理
private final boolean isStore;
  public boolean isStore() {
	return isStore;
}
然后在 getCandidate 方法中添加request.isStore()的判断过渌掉服务器的 no-Store
//isCacheable方法判断只要request或者Response设置了CacheControl属性noStore为true则不采用缓存
			if (!isCacheable(cacheResponse, request)&&!request.isStore()) {
				return new CacheStrategy(request, null);
			}
这一步通过之后就是过期时间的判断了,既然CacheControl中有maxAge属性,那么通过它操作缓存,如下:
if (requestCaching.maxAgeSeconds() != -1) {
				//这个是自己加的,如果客户端加入设置isStore为true的话
				if(request.isStore()){
					//刷新时间为
					freshMillis=SECONDS.toMillis(requestCaching.maxAgeSeconds());
				}else{
					//刷新时间为最大生命时间取最小
				freshMillis = Math.min(freshMillis,
						SECONDS.toMillis(requestCaching.maxAgeSeconds()));
				}
			}

将最大时间设置为刷新时间,也就是说如果我设置最大时间为10分钟,那么在获取缓存的时候,如果缓存保存的时间和当前的时间的差值大于10分钟则认为过期,从而达到控制缓存的效果。

最后客户端自己利用缓存框架只需要这么调用

	new OkHttpRequest.Builder().url(url).headers(headers).params(params).isStore(true).CacheControl(new CacheControl.Builder().maxAge(maxAge, TimeUnit.SECONDS).build())

当然这是对okhttp又做了一层封装,感兴趣的童鞋可以从这下载源码进行详细的阅读

okhttpClient缓存改造





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值