aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/android/volley
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/android/volley')
-rw-r--r--src/main/java/com/android/volley/Cache.java2
-rw-r--r--src/main/java/com/android/volley/CacheDispatcher.java9
-rw-r--r--src/main/java/com/android/volley/NetworkResponse.java26
-rw-r--r--src/main/java/com/android/volley/Request.java5
-rw-r--r--src/main/java/com/android/volley/Response.java16
-rw-r--r--src/main/java/com/android/volley/toolbox/DiskBasedCache.java75
-rw-r--r--src/main/java/com/android/volley/toolbox/HttpHeaderParser.java37
-rw-r--r--src/main/java/com/android/volley/toolbox/HurlStack.java41
-rw-r--r--src/main/java/com/android/volley/toolbox/ImageLoader.java2
-rw-r--r--src/main/java/com/android/volley/toolbox/Volley.java18
10 files changed, 183 insertions, 48 deletions
diff --git a/src/main/java/com/android/volley/Cache.java b/src/main/java/com/android/volley/Cache.java
index 35b2a96..b8908ac 100644
--- a/src/main/java/com/android/volley/Cache.java
+++ b/src/main/java/com/android/volley/Cache.java
@@ -16,6 +16,7 @@
package com.android.volley;
+import androidx.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -28,6 +29,7 @@ public interface Cache {
* @param key Cache key
* @return An {@link Entry} or null in the event of a cache miss
*/
+ @Nullable
Entry get(String key);
/**
diff --git a/src/main/java/com/android/volley/CacheDispatcher.java b/src/main/java/com/android/volley/CacheDispatcher.java
index be06d1f..12b1035 100644
--- a/src/main/java/com/android/volley/CacheDispatcher.java
+++ b/src/main/java/com/android/volley/CacheDispatcher.java
@@ -159,6 +159,15 @@ public class CacheDispatcher extends Thread {
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
+ if (!response.isSuccess()) {
+ request.addMarker("cache-parsing-failed");
+ mCache.invalidate(request.getCacheKey(), true);
+ request.setCacheEntry(null);
+ if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
+ mNetworkQueue.put(request);
+ }
+ return;
+ }
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
diff --git a/src/main/java/com/android/volley/NetworkResponse.java b/src/main/java/com/android/volley/NetworkResponse.java
index 01f48c6..cfbc371 100644
--- a/src/main/java/com/android/volley/NetworkResponse.java
+++ b/src/main/java/com/android/volley/NetworkResponse.java
@@ -16,6 +16,7 @@
package com.android.volley;
+import androidx.annotation.Nullable;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collections;
@@ -42,7 +43,7 @@ public class NetworkResponse {
public NetworkResponse(
int statusCode,
byte[] data,
- Map<String, String> headers,
+ @Nullable Map<String, String> headers,
boolean notModified,
long networkTimeMs) {
this(statusCode, data, headers, toAllHeaderList(headers), notModified, networkTimeMs);
@@ -62,7 +63,7 @@ public class NetworkResponse {
byte[] data,
boolean notModified,
long networkTimeMs,
- List<Header> allHeaders) {
+ @Nullable List<Header> allHeaders) {
this(statusCode, data, toHeaderMap(allHeaders), allHeaders, notModified, networkTimeMs);
}
@@ -79,7 +80,10 @@ public class NetworkResponse {
*/
@Deprecated
public NetworkResponse(
- int statusCode, byte[] data, Map<String, String> headers, boolean notModified) {
+ int statusCode,
+ byte[] data,
+ @Nullable Map<String, String> headers,
+ boolean notModified) {
this(statusCode, data, headers, notModified, /* networkTimeMs= */ 0);
}
@@ -107,7 +111,7 @@ public class NetworkResponse {
* constructor may be removed in a future release of Volley.
*/
@Deprecated
- public NetworkResponse(byte[] data, Map<String, String> headers) {
+ public NetworkResponse(byte[] data, @Nullable Map<String, String> headers) {
this(
HttpURLConnection.HTTP_OK,
data,
@@ -119,8 +123,8 @@ public class NetworkResponse {
private NetworkResponse(
int statusCode,
byte[] data,
- Map<String, String> headers,
- List<Header> allHeaders,
+ @Nullable Map<String, String> headers,
+ @Nullable List<Header> allHeaders,
boolean notModified,
long networkTimeMs) {
this.statusCode = statusCode;
@@ -150,10 +154,10 @@ public class NetworkResponse {
* map will only contain the last one. Use {@link #allHeaders} to inspect all headers returned
* by the server.
*/
- public final Map<String, String> headers;
+ @Nullable public final Map<String, String> headers;
/** All response headers. Must not be mutated directly. */
- public final List<Header> allHeaders;
+ @Nullable public final List<Header> allHeaders;
/** True if the server returned a 304 (Not Modified). */
public final boolean notModified;
@@ -161,7 +165,8 @@ public class NetworkResponse {
/** Network roundtrip time in milliseconds. */
public final long networkTimeMs;
- private static Map<String, String> toHeaderMap(List<Header> allHeaders) {
+ @Nullable
+ private static Map<String, String> toHeaderMap(@Nullable List<Header> allHeaders) {
if (allHeaders == null) {
return null;
}
@@ -176,7 +181,8 @@ public class NetworkResponse {
return headers;
}
- private static List<Header> toAllHeaderList(Map<String, String> headers) {
+ @Nullable
+ private static List<Header> toAllHeaderList(@Nullable Map<String, String> headers) {
if (headers == null) {
return null;
}
diff --git a/src/main/java/com/android/volley/Request.java b/src/main/java/com/android/volley/Request.java
index 104b046..2b53f96 100644
--- a/src/main/java/com/android/volley/Request.java
+++ b/src/main/java/com/android/volley/Request.java
@@ -115,7 +115,7 @@ public abstract class Request<T> implements Comparable<Request<T>> {
* entry will be stored here so that in the event of a "Not Modified" response, we can be sure
* it hasn't been evicted from cache.
*/
- private Cache.Entry mCacheEntry = null;
+ @Nullable private Cache.Entry mCacheEntry = null;
/** An opaque token tagging this request; used for bulk cancellation. */
private Object mTag;
@@ -319,6 +319,7 @@ public abstract class Request<T> implements Comparable<Request<T>> {
}
/** Returns the annotated cache entry, or null if there isn't one. */
+ @Nullable
public Cache.Entry getCacheEntry() {
return mCacheEntry;
}
@@ -374,6 +375,7 @@ public abstract class Request<T> implements Comparable<Request<T>> {
* @deprecated Use {@link #getParams()} instead.
*/
@Deprecated
+ @Nullable
protected Map<String, String> getPostParams() throws AuthFailureError {
return getParams();
}
@@ -431,6 +433,7 @@ public abstract class Request<T> implements Comparable<Request<T>> {
*
* @throws AuthFailureError in the event of auth failure
*/
+ @Nullable
protected Map<String, String> getParams() throws AuthFailureError {
return null;
}
diff --git a/src/main/java/com/android/volley/Response.java b/src/main/java/com/android/volley/Response.java
index 2f50e2d..622bdc4 100644
--- a/src/main/java/com/android/volley/Response.java
+++ b/src/main/java/com/android/volley/Response.java
@@ -16,6 +16,8 @@
package com.android.volley;
+import androidx.annotation.Nullable;
+
/**
* Encapsulates a parsed response for delivery.
*
@@ -39,7 +41,7 @@ public class Response<T> {
}
/** Returns a successful response containing the parsed result. */
- public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
+ public static <T> Response<T> success(@Nullable T result, @Nullable Cache.Entry cacheEntry) {
return new Response<>(result, cacheEntry);
}
@@ -51,14 +53,14 @@ public class Response<T> {
return new Response<>(error);
}
- /** Parsed response, or null in the case of error. */
- public final T result;
+ /** Parsed response, can be null; always null in the case of error. */
+ @Nullable public final T result;
- /** Cache metadata for this response, or null in the case of error. */
- public final Cache.Entry cacheEntry;
+ /** Cache metadata for this response; null if not cached or in the case of error. */
+ @Nullable public final Cache.Entry cacheEntry;
/** Detailed error information if <code>errorCode != OK</code>. */
- public final VolleyError error;
+ @Nullable public final VolleyError error;
/** True if this response was a soft-expired one and a second one MAY be coming. */
public boolean intermediate = false;
@@ -68,7 +70,7 @@ public class Response<T> {
return error == null;
}
- private Response(T result, Cache.Entry cacheEntry) {
+ private Response(@Nullable T result, @Nullable Cache.Entry cacheEntry) {
this.result = result;
this.cacheEntry = cacheEntry;
this.error = null;
diff --git a/src/main/java/com/android/volley/toolbox/DiskBasedCache.java b/src/main/java/com/android/volley/toolbox/DiskBasedCache.java
index a6a0c83..d4310e0 100644
--- a/src/main/java/com/android/volley/toolbox/DiskBasedCache.java
+++ b/src/main/java/com/android/volley/toolbox/DiskBasedCache.java
@@ -55,8 +55,8 @@ public class DiskBasedCache implements Cache {
/** Total amount of space currently used by the cache in bytes. */
private long mTotalSize = 0;
- /** The root directory to use for the cache. */
- private final File mRootDirectory;
+ /** The supplier for the root directory to use for the cache. */
+ private final FileSupplier mRootDirectorySupplier;
/** The maximum size of the cache in bytes. */
private final int mMaxCacheSizeInBytes;
@@ -78,8 +78,27 @@ public class DiskBasedCache implements Cache {
* briefly exceed this size on disk when writing a new entry that pushes it over the limit
* until the ensuing pruning completes.
*/
- public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
- mRootDirectory = rootDirectory;
+ public DiskBasedCache(final File rootDirectory, int maxCacheSizeInBytes) {
+ mRootDirectorySupplier =
+ new FileSupplier() {
+ @Override
+ public File get() {
+ return rootDirectory;
+ }
+ };
+ mMaxCacheSizeInBytes = maxCacheSizeInBytes;
+ }
+
+ /**
+ * Constructs an instance of the DiskBasedCache at the specified directory.
+ *
+ * @param rootDirectorySupplier The supplier for the root directory of the cache.
+ * @param maxCacheSizeInBytes The maximum size of the cache in bytes. Note that the cache may
+ * briefly exceed this size on disk when writing a new entry that pushes it over the limit
+ * until the ensuing pruning completes.
+ */
+ public DiskBasedCache(FileSupplier rootDirectorySupplier, int maxCacheSizeInBytes) {
+ mRootDirectorySupplier = rootDirectorySupplier;
mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}
@@ -93,10 +112,20 @@ public class DiskBasedCache implements Cache {
this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
}
+ /**
+ * Constructs an instance of the DiskBasedCache at the specified directory using the default
+ * maximum cache size of 5MB.
+ *
+ * @param rootDirectorySupplier The supplier for the root directory of the cache.
+ */
+ public DiskBasedCache(FileSupplier rootDirectorySupplier) {
+ this(rootDirectorySupplier, DEFAULT_DISK_USAGE_BYTES);
+ }
+
/** Clears the cache. Deletes all cached files from disk. */
@Override
public synchronized void clear() {
- File[] files = mRootDirectory.listFiles();
+ File[] files = mRootDirectorySupplier.get().listFiles();
if (files != null) {
for (File file : files) {
file.delete();
@@ -150,13 +179,14 @@ public class DiskBasedCache implements Cache {
*/
@Override
public synchronized void initialize() {
- if (!mRootDirectory.exists()) {
- if (!mRootDirectory.mkdirs()) {
- VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
+ File rootDirectory = mRootDirectorySupplier.get();
+ if (!rootDirectory.exists()) {
+ if (!rootDirectory.mkdirs()) {
+ VolleyLog.e("Unable to create cache dir %s", rootDirectory.getAbsolutePath());
}
return;
}
- File[] files = mRootDirectory.listFiles();
+ File[] files = rootDirectory.listFiles();
if (files == null) {
return;
}
@@ -226,12 +256,12 @@ public class DiskBasedCache implements Cache {
e.size = file.length();
putEntry(key, e);
pruneIfNeeded();
- return;
} catch (IOException e) {
- }
- boolean deleted = file.delete();
- if (!deleted) {
- VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
+ boolean deleted = file.delete();
+ if (!deleted) {
+ VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
+ }
+ initializeIfRootDirectoryDeleted();
}
}
@@ -262,7 +292,22 @@ public class DiskBasedCache implements Cache {
/** Returns a file object for the given cache key. */
public File getFileForKey(String key) {
- return new File(mRootDirectory, getFilenameForKey(key));
+ return new File(mRootDirectorySupplier.get(), getFilenameForKey(key));
+ }
+
+ /** Re-initialize the cache if the directory was deleted. */
+ private void initializeIfRootDirectoryDeleted() {
+ if (!mRootDirectorySupplier.get().exists()) {
+ VolleyLog.d("Re-initializing cache after external clearing.");
+ mEntries.clear();
+ mTotalSize = 0;
+ initialize();
+ }
+ }
+
+ /** Represents a supplier for {@link File}s. */
+ public interface FileSupplier {
+ File get();
}
/** Prunes the cache to fit the maximum size. */
diff --git a/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java b/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java
index 27d1268..1b410af 100644
--- a/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java
+++ b/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java
@@ -16,6 +16,7 @@
package com.android.volley.toolbox;
+import androidx.annotation.Nullable;
import com.android.volley.Cache;
import com.android.volley.Header;
import com.android.volley.NetworkResponse;
@@ -37,7 +38,11 @@ public class HttpHeaderParser {
private static final String DEFAULT_CONTENT_CHARSET = "ISO-8859-1";
- private static final String RFC1123_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
+ private static final String RFC1123_PARSE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
+
+ // Hardcode 'GMT' rather than using 'zzz' since some platforms append an extraneous +00:00.
+ // See #287.
+ private static final String RFC1123_OUTPUT_FORMAT = "EEE, dd MMM yyyy HH:mm:ss 'GMT'";
/**
* Extracts a {@link com.android.volley.Cache.Entry} from a {@link NetworkResponse}.
@@ -45,10 +50,14 @@ public class HttpHeaderParser {
* @param response The network response to parse headers from
* @return a cache entry for the given response, or null if the response is not cacheable.
*/
+ @Nullable
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
long now = System.currentTimeMillis();
Map<String, String> headers = response.headers;
+ if (headers == null) {
+ return null;
+ }
long serverDate = 0;
long lastModified = 0;
@@ -132,21 +141,29 @@ public class HttpHeaderParser {
public static long parseDateAsEpoch(String dateStr) {
try {
// Parse date in RFC1123 format if this header contains one
- return newRfc1123Formatter().parse(dateStr).getTime();
+ return newUsGmtFormatter(RFC1123_PARSE_FORMAT).parse(dateStr).getTime();
} catch (ParseException e) {
// Date in invalid format, fallback to 0
- VolleyLog.e(e, "Unable to parse dateStr: %s, falling back to 0", dateStr);
+ // If the value is either "0" or "-1" we only log to verbose,
+ // these values are pretty common and cause log spam.
+ String message = "Unable to parse dateStr: %s, falling back to 0";
+ if ("0".equals(dateStr) || "-1".equals(dateStr)) {
+ VolleyLog.v(message, dateStr);
+ } else {
+ VolleyLog.e(e, message, dateStr);
+ }
+
return 0;
}
}
/** Format an epoch date in RFC1123 format. */
static String formatEpochAsRfc1123(long epoch) {
- return newRfc1123Formatter().format(new Date(epoch));
+ return newUsGmtFormatter(RFC1123_OUTPUT_FORMAT).format(new Date(epoch));
}
- private static SimpleDateFormat newRfc1123Formatter() {
- SimpleDateFormat formatter = new SimpleDateFormat(RFC1123_FORMAT, Locale.US);
+ private static SimpleDateFormat newUsGmtFormatter(String format) {
+ SimpleDateFormat formatter = new SimpleDateFormat(format, Locale.US);
formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
return formatter;
}
@@ -159,7 +176,11 @@ public class HttpHeaderParser {
* @return Returns the charset specified in the Content-Type of this header, or the
* defaultCharset if none can be found.
*/
- public static String parseCharset(Map<String, String> headers, String defaultCharset) {
+ public static String parseCharset(
+ @Nullable Map<String, String> headers, String defaultCharset) {
+ if (headers == null) {
+ return defaultCharset;
+ }
String contentType = headers.get(HEADER_CONTENT_TYPE);
if (contentType != null) {
String[] params = contentType.split(";", 0);
@@ -180,7 +201,7 @@ public class HttpHeaderParser {
* Returns the charset specified in the Content-Type of this header, or the HTTP default
* (ISO-8859-1) if none can be found.
*/
- public static String parseCharset(Map<String, String> headers) {
+ public static String parseCharset(@Nullable Map<String, String> headers) {
return parseCharset(headers, DEFAULT_CONTENT_CHARSET);
}
diff --git a/src/main/java/com/android/volley/toolbox/HurlStack.java b/src/main/java/com/android/volley/toolbox/HurlStack.java
index f85d42c..9c38023 100644
--- a/src/main/java/com/android/volley/toolbox/HurlStack.java
+++ b/src/main/java/com/android/volley/toolbox/HurlStack.java
@@ -25,6 +25,7 @@ import java.io.DataOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
@@ -111,7 +112,7 @@ public class HurlStack extends BaseHttpStack {
responseCode,
convertHeaders(connection.getHeaderFields()),
connection.getContentLength(),
- new UrlConnectionInputStream(connection));
+ createInputStream(request, connection));
} finally {
if (!keepConnectionOpen) {
connection.disconnect();
@@ -169,6 +170,19 @@ public class HurlStack extends BaseHttpStack {
}
/**
+ * Create and return an InputStream from which the response will be read.
+ *
+ * <p>May be overridden by subclasses to manipulate or monitor this input stream.
+ *
+ * @param request current request.
+ * @param connection current connection of request.
+ * @return an InputStream from which the response will be read.
+ */
+ protected InputStream createInputStream(Request<?> request, HttpURLConnection connection) {
+ return new UrlConnectionInputStream(connection);
+ }
+
+ /**
* Initializes an {@link InputStream} from the given {@link HttpURLConnection}.
*
* @param connection
@@ -223,7 +237,7 @@ public class HurlStack extends BaseHttpStack {
// NOTE: Any request headers added here (via setRequestProperty or addRequestProperty) should be
// checked against the existing properties in the connection and not overridden if already set.
@SuppressWarnings("deprecation")
- /* package */ static void setConnectionParametersForRequest(
+ /* package */ void setConnectionParametersForRequest(
HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError {
switch (request.getMethod()) {
case Method.DEPRECATED_GET_OR_POST:
@@ -270,7 +284,7 @@ public class HurlStack extends BaseHttpStack {
}
}
- private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)
+ private void addBodyIfExists(HttpURLConnection connection, Request<?> request)
throws IOException, AuthFailureError {
byte[] body = request.getBody();
if (body != null) {
@@ -278,7 +292,7 @@ public class HurlStack extends BaseHttpStack {
}
}
- private static void addBody(HttpURLConnection connection, Request<?> request, byte[] body)
+ private void addBody(HttpURLConnection connection, Request<?> request, byte[] body)
throws IOException {
// Prepare output. There is no need to set Content-Length explicitly,
// since this is handled by HttpURLConnection using the size of the prepared
@@ -289,8 +303,25 @@ public class HurlStack extends BaseHttpStack {
connection.setRequestProperty(
HttpHeaderParser.HEADER_CONTENT_TYPE, request.getBodyContentType());
}
- DataOutputStream out = new DataOutputStream(connection.getOutputStream());
+ DataOutputStream out =
+ new DataOutputStream(createOutputStream(request, connection, body.length));
out.write(body);
out.close();
}
+
+ /**
+ * Create and return an OutputStream to which the request body will be written.
+ *
+ * <p>May be overridden by subclasses to manipulate or monitor this output stream.
+ *
+ * @param request current request.
+ * @param connection current connection of request.
+ * @param length size of stream to write.
+ * @return an OutputStream to which the request body will be written.
+ * @throws IOException if an I/O error occurs while creating the stream.
+ */
+ protected OutputStream createOutputStream(
+ Request<?> request, HttpURLConnection connection, int length) throws IOException {
+ return connection.getOutputStream();
+ }
}
diff --git a/src/main/java/com/android/volley/toolbox/ImageLoader.java b/src/main/java/com/android/volley/toolbox/ImageLoader.java
index b80072b..eece2cf 100644
--- a/src/main/java/com/android/volley/toolbox/ImageLoader.java
+++ b/src/main/java/com/android/volley/toolbox/ImageLoader.java
@@ -20,6 +20,7 @@ import android.os.Looper;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import androidx.annotation.MainThread;
+import androidx.annotation.Nullable;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response.ErrorListener;
@@ -70,6 +71,7 @@ public class ImageLoader {
* LruCache is recommended.
*/
public interface ImageCache {
+ @Nullable
Bitmap getBitmap(String url);
void putBitmap(String url, Bitmap bitmap);
diff --git a/src/main/java/com/android/volley/toolbox/Volley.java b/src/main/java/com/android/volley/toolbox/Volley.java
index 1982802..bc65c9c 100644
--- a/src/main/java/com/android/volley/toolbox/Volley.java
+++ b/src/main/java/com/android/volley/toolbox/Volley.java
@@ -86,8 +86,22 @@ public class Volley {
}
private static RequestQueue newRequestQueue(Context context, Network network) {
- File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
- RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
+ final Context appContext = context.getApplicationContext();
+ // Use a lazy supplier for the cache directory so that newRequestQueue() can be called on
+ // main thread without causing strict mode violation.
+ DiskBasedCache.FileSupplier cacheSupplier =
+ new DiskBasedCache.FileSupplier() {
+ private File cacheDir = null;
+
+ @Override
+ public File get() {
+ if (cacheDir == null) {
+ cacheDir = new File(appContext.getCacheDir(), DEFAULT_CACHE_DIR);
+ }
+ return cacheDir;
+ }
+ };
+ RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheSupplier), network);
queue.start();
return queue;
}