• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.http;
18 
19 import com.android.okhttp.Cache;
20 import com.android.okhttp.AndroidShimResponseCache;
21 import com.android.okhttp.OkCacheContainer;
22 
23 import java.io.Closeable;
24 import java.io.File;
25 import java.io.IOException;
26 import java.net.CacheRequest;
27 import java.net.CacheResponse;
28 import java.net.ResponseCache;
29 import java.net.URI;
30 import java.net.URLConnection;
31 import java.util.List;
32 import java.util.Map;
33 
34 /**
35  * Caches HTTP and HTTPS responses to the filesystem so they may be reused,
36  * saving time and bandwidth. This class supports {@link
37  * java.net.HttpURLConnection} and {@link javax.net.ssl.HttpsURLConnection};
38  * there is no platform-provided cache for {@code DefaultHttpClient} or
39  * {@code AndroidHttpClient}. Installation and instances are thread
40  * safe.
41  *
42  * <h3>Installing an HTTP response cache</h3>
43  * Enable caching of all of your application's HTTP requests by installing the
44  * cache at application startup. For example, this code installs a 10 MiB cache
45  * in the {@link android.content.Context#getCacheDir() application-specific
46  * cache directory} of the filesystem}: <pre>   {@code
47  *   protected void onCreate(Bundle savedInstanceState) {
48  *       ...
49  *
50  *       try {
51  *           File httpCacheDir = new File(context.getCacheDir(), "http");
52  *           long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
53  *           HttpResponseCache.install(httpCacheDir, httpCacheSize);
54  *       } catch (IOException e) {
55  *           Log.i(TAG, "HTTP response cache installation failed:" + e);
56  *       }
57  *   }
58  *
59  *   protected void onStop() {
60  *       ...
61  *
62  *       HttpResponseCache cache = HttpResponseCache.getInstalled();
63  *       if (cache != null) {
64  *           cache.flush();
65  *       }
66  *   }}</pre>
67  * This cache will evict entries as necessary to keep its size from exceeding
68  * 10 MiB. The best cache size is application specific and depends on the size
69  * and frequency of the files being downloaded. Increasing the limit may improve
70  * the hit rate, but it may also just waste filesystem space!
71  *
72  * <p>For some applications it may be preferable to create the cache in the
73  * external storage directory. <strong>There are no access controls on the
74  * external storage directory so it should not be used for caches that could
75  * contain private data.</strong> Although it often has more free space,
76  * external storage is optional and&#8212;even if available&#8212;can disappear
77  * during use. Retrieve the external cache directory using {@link
78  * android.content.Context#getExternalCacheDir()}. If this method returns null,
79  * your application should fall back to either not caching or caching on
80  * non-external storage. If the external storage is removed during use, the
81  * cache hit rate will drop to zero and ongoing cache reads will fail.
82  *
83  * <p>Flushing the cache forces its data to the filesystem. This ensures that
84  * all responses written to the cache will be readable the next time the
85  * activity starts.
86  *
87  * <h3>Cache Optimization</h3>
88  * To measure cache effectiveness, this class tracks three statistics:
89  * <ul>
90  *     <li><strong>{@link #getRequestCount() Request Count:}</strong> the number
91  *         of HTTP requests issued since this cache was created.
92  *     <li><strong>{@link #getNetworkCount() Network Count:}</strong> the
93  *         number of those requests that required network use.
94  *     <li><strong>{@link #getHitCount() Hit Count:}</strong> the number of
95  *         those requests whose responses were served by the cache.
96  * </ul>
97  * Sometimes a request will result in a conditional cache hit. If the cache
98  * contains a stale copy of the response, the client will issue a conditional
99  * {@code GET}. The server will then send either the updated response if it has
100  * changed, or a short 'not modified' response if the client's copy is still
101  * valid. Such responses increment both the network count and hit count.
102  *
103  * <p>The best way to improve the cache hit rate is by configuring the web
104  * server to return cacheable responses. Although this client honors all <a
105  * href="http://www.ietf.org/rfc/rfc2616.txt">HTTP/1.1 (RFC 2068)</a> cache
106  * headers, it doesn't cache partial responses.
107  *
108  * <h3>Force a Network Response</h3>
109  * In some situations, such as after a user clicks a 'refresh' button, it may be
110  * necessary to skip the cache, and fetch data directly from the server. To force
111  * a full refresh, add the {@code no-cache} directive: <pre>   {@code
112  *         connection.addRequestProperty("Cache-Control", "no-cache");
113  * }</pre>
114  * If it is only necessary to force a cached response to be validated by the
115  * server, use the more efficient {@code max-age=0} instead: <pre>   {@code
116  *         connection.addRequestProperty("Cache-Control", "max-age=0");
117  * }</pre>
118  *
119  * <h3>Force a Cache Response</h3>
120  * Sometimes you'll want to show resources if they are available immediately,
121  * but not otherwise. This can be used so your application can show
122  * <i>something</i> while waiting for the latest data to be downloaded. To
123  * restrict a request to locally-cached resources, add the {@code
124  * only-if-cached} directive: <pre>   {@code
125  *     try {
126  *         connection.addRequestProperty("Cache-Control", "only-if-cached");
127  *         InputStream cached = connection.getInputStream();
128  *         // the resource was cached! show it
129  *     } catch (FileNotFoundException e) {
130  *         // the resource was not cached
131  *     }
132  * }</pre>
133  * This technique works even better in situations where a stale response is
134  * better than no response. To permit stale cached responses, use the {@code
135  * max-stale} directive with the maximum staleness in seconds: <pre>   {@code
136  *         int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
137  *         connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale);
138  * }</pre>
139  *
140  * <h3>Working With Earlier Releases</h3>
141  * This class was added in Android 4.0 (Ice Cream Sandwich). Use reflection to
142  * enable the response cache without impacting earlier releases: <pre>   {@code
143  *       try {
144  *           File httpCacheDir = new File(context.getCacheDir(), "http");
145  *           long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
146  *           Class.forName("android.net.http.HttpResponseCache")
147  *                   .getMethod("install", File.class, long.class)
148  *                   .invoke(null, httpCacheDir, httpCacheSize);
149  *       } catch (Exception httpResponseCacheNotAvailable) {
150  *       }}</pre>
151  */
152 public final class HttpResponseCache extends ResponseCache implements Closeable, OkCacheContainer {
153 
154     private final AndroidShimResponseCache delegate;
155 
HttpResponseCache(AndroidShimResponseCache delegate)156     private HttpResponseCache(AndroidShimResponseCache delegate) {
157         this.delegate = delegate;
158     }
159 
160     /**
161      * Returns the currently-installed {@code HttpResponseCache}, or null if
162      * there is no cache installed or it is not a {@code HttpResponseCache}.
163      */
getInstalled()164     public static HttpResponseCache getInstalled() {
165         ResponseCache installed = ResponseCache.getDefault();
166         if (installed instanceof HttpResponseCache) {
167             return (HttpResponseCache) installed;
168         }
169         return null;
170     }
171 
172     /**
173      * Creates a new HTTP response cache and sets it as the system default cache.
174      *
175      * @param directory the directory to hold cache data.
176      * @param maxSize the maximum size of the cache in bytes.
177      * @return the newly-installed cache
178      * @throws IOException if {@code directory} cannot be used for this cache.
179      *     Most applications should respond to this exception by logging a
180      *     warning.
181      */
install(File directory, long maxSize)182     public static synchronized HttpResponseCache install(File directory, long maxSize)
183             throws IOException {
184         ResponseCache installed = ResponseCache.getDefault();
185         if (installed instanceof HttpResponseCache) {
186             HttpResponseCache installedResponseCache = (HttpResponseCache) installed;
187             // don't close and reopen if an equivalent cache is already installed
188             AndroidShimResponseCache trueResponseCache = installedResponseCache.delegate;
189             if (trueResponseCache.isEquivalent(directory, maxSize)) {
190                 return installedResponseCache;
191             } else {
192                 // The HttpResponseCache that owns this object is about to be replaced.
193                 trueResponseCache.close();
194             }
195         }
196 
197         AndroidShimResponseCache trueResponseCache =
198                 AndroidShimResponseCache.create(directory, maxSize);
199         HttpResponseCache newResponseCache = new HttpResponseCache(trueResponseCache);
200         ResponseCache.setDefault(newResponseCache);
201         return newResponseCache;
202     }
203 
get(URI uri, String requestMethod, Map<String, List<String>> requestHeaders)204     @Override public CacheResponse get(URI uri, String requestMethod,
205             Map<String, List<String>> requestHeaders) throws IOException {
206         return delegate.get(uri, requestMethod, requestHeaders);
207     }
208 
put(URI uri, URLConnection urlConnection)209     @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
210         return delegate.put(uri, urlConnection);
211     }
212 
213     /**
214      * Returns the number of bytes currently being used to store the values in
215      * this cache. This may be greater than the {@link #maxSize} if a background
216      * deletion is pending. {@code -1} is returned if the size cannot be determined.
217      */
size()218     public long size() {
219         try {
220             return delegate.size();
221         } catch (IOException e) {
222             // This can occur if the cache failed to lazily initialize.
223             return -1;
224         }
225     }
226 
227     /**
228      * Returns the maximum number of bytes that this cache should use to store
229      * its data.
230      */
maxSize()231     public long maxSize() {
232         return delegate.maxSize();
233     }
234 
235     /**
236      * Force buffered operations to the filesystem. This ensures that responses
237      * written to the cache will be available the next time the cache is opened,
238      * even if this process is killed.
239      */
flush()240     public void flush() {
241         try {
242             delegate.flush();
243         } catch (IOException ignored) {
244         }
245     }
246 
247     /**
248      * Returns the number of HTTP requests that required the network to either
249      * supply a response or validate a locally cached response.
250      */
getNetworkCount()251     public int getNetworkCount() {
252         return delegate.getNetworkCount();
253     }
254 
255     /**
256      * Returns the number of HTTP requests whose response was provided by the
257      * cache. This may include conditional {@code GET} requests that were
258      * validated over the network.
259      */
getHitCount()260     public int getHitCount() {
261         return delegate.getHitCount();
262     }
263 
264     /**
265      * Returns the total number of HTTP requests that were made. This includes
266      * both client requests and requests that were made on the client's behalf
267      * to handle a redirects and retries.
268      */
getRequestCount()269     public int getRequestCount() {
270         return delegate.getRequestCount();
271     }
272 
273     /**
274      * Uninstalls the cache and releases any active resources. Stored contents
275      * will remain on the filesystem.
276      */
close()277     @Override public void close() throws IOException {
278         if (ResponseCache.getDefault() == this) {
279             ResponseCache.setDefault(null);
280         }
281         delegate.close();
282     }
283 
284     /**
285      * Uninstalls the cache and deletes all of its stored contents.
286      */
delete()287     public void delete() throws IOException {
288         if (ResponseCache.getDefault() == this) {
289             ResponseCache.setDefault(null);
290         }
291         delegate.delete();
292     }
293 
294     /** @hide Needed for OkHttp integration. */
295     @Override
getCache()296     public Cache getCache() {
297         return delegate.getCache();
298     }
299 
300 }
301