headers) {
135 | String contentType = headers.get(HTTP.CONTENT_TYPE);
136 | if (contentType != null) {
137 | String[] params = contentType.split(";");
138 | for (int i = 1; i < params.length; i++) {
139 | String[] pair = params[i].trim().split("=");
140 | if (pair.length == 2) {
141 | if (pair[0].equals("charset")) {
142 | return pair[1];
143 | }
144 | }
145 | }
146 | }
147 |
148 | return HTTP.DEFAULT_CONTENT_CHARSET;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/requestvolley/src/main/java/com/jude/volley/toolbox/ByteArrayPool.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 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 com.jude.volley.toolbox;
18 |
19 | import java.util.ArrayList;
20 | import java.util.Collections;
21 | import java.util.Comparator;
22 | import java.util.LinkedList;
23 | import java.util.List;
24 |
25 | /**
26 | * ByteArrayPool is a source and repository of byte[] objects. Its purpose is to
27 | * supply those buffers to consumers who need to use them for a short period of time and then
28 | * dispose of them. Simply creating and disposing such buffers in the conventional manner can
29 | * considerable heap churn and garbage collection delays on Android, which lacks good management of
30 | * short-lived heap objects. It may be advantageous to trade off some memory in the form of a
31 | * permanently allocated pool of buffers in order to gain heap performance improvements; that is
32 | * what this class does.
33 | *
34 | * A good candidate user for this class is something like an I/O system that uses large temporary
35 | * byte[] buffers to copy data around. In these use cases, often the consumer wants
36 | * the buffer to be a certain minimum size to ensure good performance (e.g. when copying data chunks
37 | * off of a stream), but doesn't mind if the buffer is larger than the minimum. Taking this into
38 | * account and also to maximize the odds of being able to reuse a recycled buffer, this class is
39 | * free to return buffers larger than the requested size. The caller needs to be able to gracefully
40 | * deal with getting buffers any size over the minimum.
41 | *
42 | * If there is not a suitably-sized buffer in its recycling pool when a buffer is requested, this
43 | * class will allocate a new buffer and return it.
44 | *
45 | * This class has no special ownership of buffers it creates; the caller is free to take a buffer
46 | * it receives from this pool, use it permanently, and never return it to the pool; additionally,
47 | * it is not harmful to return to this pool a buffer that was allocated elsewhere, provided there
48 | * are no other lingering references to it.
49 | *
50 | * This class ensures that the total size of the buffers in its recycling pool never exceeds a
51 | * certain byte limit. When a buffer is returned that would cause the pool to exceed the limit,
52 | * least-recently-used buffers are disposed.
53 | */
54 | public class ByteArrayPool {
55 | /** The buffer pool, arranged both by last use and by buffer size */
56 | private List mBuffersByLastUse = new LinkedList();
57 | private List mBuffersBySize = new ArrayList(64);
58 |
59 | /** The total size of the buffers in the pool */
60 | private int mCurrentSize = 0;
61 |
62 | /**
63 | * The maximum aggregate size of the buffers in the pool. Old buffers are discarded to stay
64 | * under this limit.
65 | */
66 | private final int mSizeLimit;
67 |
68 | /** Compares buffers by size */
69 | protected static final Comparator BUF_COMPARATOR = new Comparator() {
70 | @Override
71 | public int compare(byte[] lhs, byte[] rhs) {
72 | return lhs.length - rhs.length;
73 | }
74 | };
75 |
76 | /**
77 | * @param sizeLimit the maximum size of the pool, in bytes
78 | */
79 | public ByteArrayPool(int sizeLimit) {
80 | mSizeLimit = sizeLimit;
81 | }
82 |
83 | /**
84 | * Returns a buffer from the pool if one is available in the requested size, or allocates a new
85 | * one if a pooled one is not available.
86 | *
87 | * @param len the minimum size, in bytes, of the requested buffer. The returned buffer may be
88 | * larger.
89 | * @return a byte[] buffer is always returned.
90 | */
91 | public synchronized byte[] getBuf(int len) {
92 | for (int i = 0; i < mBuffersBySize.size(); i++) {
93 | byte[] buf = mBuffersBySize.get(i);
94 | if (buf.length >= len) {
95 | mCurrentSize -= buf.length;
96 | mBuffersBySize.remove(i);
97 | mBuffersByLastUse.remove(buf);
98 | return buf;
99 | }
100 | }
101 | return new byte[len];
102 | }
103 |
104 | /**
105 | * Returns a buffer to the pool, throwing away old buffers if the pool would exceed its allotted
106 | * size.
107 | *
108 | * @param buf the buffer to return to the pool.
109 | */
110 | public synchronized void returnBuf(byte[] buf) {
111 | if (buf == null || buf.length > mSizeLimit) {
112 | return;
113 | }
114 | mBuffersByLastUse.add(buf);
115 | int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
116 | if (pos < 0) {
117 | pos = -pos - 1;
118 | }
119 | mBuffersBySize.add(pos, buf);
120 | mCurrentSize += buf.length;
121 | trim();
122 | }
123 |
124 | /**
125 | * Removes buffers from the pool until it is under its size limit.
126 | */
127 | private synchronized void trim() {
128 | while (mCurrentSize > mSizeLimit) {
129 | byte[] buf = mBuffersByLastUse.remove(0);
130 | mBuffersBySize.remove(buf);
131 | mCurrentSize -= buf.length;
132 | }
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/requestvolley/src/main/java/com/jude/volley/NetworkDispatcher.java:
--------------------------------------------------------------------------------
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 com.jude.volley;
18 |
19 | import android.annotation.TargetApi;
20 | import android.net.TrafficStats;
21 | import android.os.Build;
22 | import android.os.Process;
23 |
24 | import java.util.concurrent.BlockingQueue;
25 |
26 | /**
27 | * Provides a thread for performing network dispatch from a queue of requests.
28 | *
29 | * Requests added to the specified queue are processed from the network via a
30 | * specified {@link Network} interface. Responses are committed to cache, if
31 | * eligible, using a specified {@link Cache} interface. Valid responses and
32 | * errors are posted back to the caller via a {@link ResponseDelivery}.
33 | */
34 | public class NetworkDispatcher extends Thread {
35 | /** The queue of requests to service. */
36 | private final BlockingQueue> mQueue;
37 | /** The network interface for processing requests. */
38 | private final Network mNetwork;
39 | /** The cache to write to. */
40 | private final Cache mCache;
41 | /** For posting responses and errors. */
42 | private final ResponseDelivery mDelivery;
43 | /** Used for telling us to die. */
44 | private volatile boolean mQuit = false;
45 |
46 | /**
47 | * Creates a new network dispatcher thread. You must call {@link #start()}
48 | * in order to begin processing.
49 | *
50 | * @param queue Queue of incoming requests for triage
51 | * @param network Network interface to use for performing requests
52 | * @param cache Cache interface to use for writing responses to cache
53 | * @param delivery Delivery interface to use for posting responses
54 | */
55 | public NetworkDispatcher(BlockingQueue> queue,
56 | Network network, Cache cache,
57 | ResponseDelivery delivery) {
58 | mQueue = queue;
59 | mNetwork = network;
60 | mCache = cache;
61 | mDelivery = delivery;
62 | }
63 |
64 | /**
65 | * Forces this dispatcher to quit immediately. If any requests are still in
66 | * the queue, they are not guaranteed to be processed.
67 | */
68 | public void quit() {
69 | mQuit = true;
70 | interrupt();
71 | }
72 |
73 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
74 | private void addTrafficStatsTag(Request> request) {
75 | // Tag the request (if API >= 14)
76 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
77 | TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
78 | }
79 | }
80 |
81 | @Override
82 | public void run() {
83 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
84 | Request> request;
85 | while (true) {
86 | try {
87 | // Take a request from the queue.
88 | request = mQueue.take();
89 | } catch (InterruptedException e) {
90 | // We may have been interrupted because it was time to quit.
91 | if (mQuit) {
92 | return;
93 | }
94 | continue;
95 | }
96 |
97 | try {
98 | request.addMarker("network-queue-take");
99 |
100 | // If the request was cancelled already, do not perform the
101 | // network request.
102 | if (request.isCanceled()) {
103 | request.finish("network-discard-cancelled");
104 | continue;
105 | }
106 |
107 | addTrafficStatsTag(request);
108 |
109 | // Perform the network request.
110 | NetworkResponse networkResponse = mNetwork.performRequest(request);
111 | request.addMarker("network-http-complete");
112 |
113 | // If the server returned 304 AND we delivered a response already,
114 | // we're done -- don't deliver a second identical response.
115 | if (networkResponse.notModified && request.hasHadResponseDelivered()) {
116 | request.finish("not-modified");
117 | continue;
118 | }
119 |
120 | // Parse the response here on the worker thread.
121 | Response> response = request.parseNetworkResponse(networkResponse);
122 | request.addMarker("network-parse-complete");
123 |
124 | // Write to cache if applicable.
125 | // TODO: Only update cache metadata instead of entire record for 304s.
126 | if (request.shouldCache() && response.cacheEntry != null) {
127 | mCache.put(request.getCacheKey(), response.cacheEntry);
128 | request.addMarker("network-cache-written");
129 | }else{
130 | if (request.shouldCache()){
131 | request.addMarker("network-shouldn't cache");
132 | }
133 | if (response.cacheEntry != null){
134 | request.addMarker("network-hasn't cacheEntry");
135 | }
136 | }
137 |
138 | // Post the response back.
139 | request.markDelivered();
140 | mDelivery.postResponse(request, response);
141 | } catch (VolleyError volleyError) {
142 | parseAndDeliverNetworkError(request, volleyError);
143 | } catch (Exception e) {
144 | VolleyLog.e(e, "Unhandled exception %s", e.toString());
145 | mDelivery.postError(request, new VolleyError(e));
146 | }
147 | }
148 | }
149 |
150 | private void parseAndDeliverNetworkError(Request> request, VolleyError error) {
151 | error = request.parseNetworkError(error);
152 | mDelivery.postError(request, error);
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/requestvolley/src/main/java/com/jude/volley/CacheDispatcher.java:
--------------------------------------------------------------------------------
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 com.jude.volley;
18 |
19 | import android.os.Process;
20 |
21 | import java.util.concurrent.BlockingQueue;
22 |
23 | /**
24 | * Provides a thread for performing cache triage on a queue of requests.
25 | *
26 | * Requests added to the specified cache queue are resolved from cache.
27 | * Any deliverable response is posted back to the caller via a
28 | * {@link ResponseDelivery}. Cache misses and responses that require
29 | * refresh are enqueued on the specified network queue for processing
30 | * by a {@link NetworkDispatcher}.
31 | */
32 | public class CacheDispatcher extends Thread {
33 |
34 | private static final boolean DEBUG = VolleyLog.DEBUG;
35 |
36 | /** The queue of requests coming in for triage. */
37 | private final BlockingQueue> mCacheQueue;
38 |
39 | /** The queue of requests going out to the network. */
40 | private final BlockingQueue> mNetworkQueue;
41 |
42 | /** The cache to read from. */
43 | private final Cache mCache;
44 |
45 | /** For posting responses. */
46 | private final ResponseDelivery mDelivery;
47 |
48 | /** Used for telling us to die. */
49 | private volatile boolean mQuit = false;
50 |
51 | /**
52 | * Creates a new cache triage dispatcher thread. You must call {@link #start()}
53 | * in order to begin processing.
54 | *
55 | * @param cacheQueue Queue of incoming requests for triage
56 | * @param networkQueue Queue to post requests that require network to
57 | * @param cache Cache interface to use for resolution
58 | * @param delivery Delivery interface to use for posting responses
59 | */
60 | public CacheDispatcher(
61 | BlockingQueue> cacheQueue, BlockingQueue> networkQueue,
62 | Cache cache, ResponseDelivery delivery) {
63 | mCacheQueue = cacheQueue;
64 | mNetworkQueue = networkQueue;
65 | mCache = cache;
66 | mDelivery = delivery;
67 | }
68 |
69 | /**
70 | * Forces this dispatcher to quit immediately. If any requests are still in
71 | * the queue, they are not guaranteed to be processed.
72 | */
73 | public void quit() {
74 | mQuit = true;
75 | interrupt();
76 | }
77 |
78 | @Override
79 | public void run() {
80 | if (DEBUG) VolleyLog.v("start new dispatcher");
81 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
82 |
83 | // Make a blocking call to initialize the cache.
84 | mCache.initialize();
85 |
86 | while (true) {
87 | try {
88 | // Get a request from the cache triage queue, blocking until
89 | // at least one is available.
90 | final Request> request = mCacheQueue.take();
91 | request.addMarker("cache-queue-take");
92 |
93 | // If the request has been canceled, don't bother dispatching it.
94 | if (request.isCanceled()) {
95 | request.finish("cache-discard-canceled");
96 | continue;
97 | }
98 |
99 | // Attempt to retrieve this item from cache.
100 | Cache.Entry entry = mCache.get(request.getCacheKey());
101 | if (entry == null) {
102 | request.addMarker("cache-miss");
103 | // Cache miss; send off to the network dispatcher.
104 | mNetworkQueue.put(request);
105 | continue;
106 | }
107 |
108 | // If it is completely expired, just send it to the network.
109 | if (entry.isExpired()) {
110 | request.addMarker("cache-hit-expired");
111 | request.setCacheEntry(entry);
112 | mNetworkQueue.put(request);
113 | continue;
114 | }
115 |
116 | // We have a cache hit; parse its data for delivery back to the request.
117 | request.addMarker("cache-hit");
118 | Response> response = request.parseNetworkResponse(
119 | new NetworkResponse(entry.data, entry.responseHeaders));
120 | request.addMarker("cache-hit-parsed");
121 |
122 | if (!entry.refreshNeeded()) {
123 | // Completely unexpired cache hit. Just deliver the response.
124 | mDelivery.postResponse(request, response);
125 | } else {
126 | // Soft-expired cache hit. We can deliver the cached response,
127 | // but we need to also send the request to the network for
128 | // refreshing.
129 | request.addMarker("cache-hit-refresh-needed");
130 | request.setCacheEntry(entry);
131 |
132 | // Mark the response as intermediate.
133 | response.intermediate = true;
134 |
135 | // Post the intermediate response back to the user and have
136 | // the delivery then forward the request along to the network.
137 | mDelivery.postResponse(request, response, new Runnable() {
138 | @Override
139 | public void run() {
140 | try {
141 | mNetworkQueue.put(request);
142 | } catch (InterruptedException e) {
143 | // Not much we can do about this.
144 | }
145 | }
146 | });
147 | }
148 |
149 | } catch (InterruptedException e) {
150 | // We may have been interrupted because it was time to quit.
151 | if (mQuit) {
152 | return;
153 | }
154 | continue;
155 | }
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/requestvolley/src/main/java/com/jude/volley/VolleyLog.java:
--------------------------------------------------------------------------------
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 com.jude.volley;
18 |
19 | import android.os.SystemClock;
20 | import android.util.Log;
21 |
22 | import java.util.ArrayList;
23 | import java.util.List;
24 | import java.util.Locale;
25 |
26 | /** Logging helper class. */
27 | public class VolleyLog {
28 | public static String TAG = "Volley";
29 |
30 | public static boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
31 |
32 | /**
33 | * Customize the log tag for your application, so that other apps
34 | * using Volley don't mix their logs with yours.
35 | * Enable the log property for your tag before starting your app:
36 | * {@code adb shell setprop log.tag.<tag>}
37 | */
38 | public static void setTag(String tag) {
39 | d("Changing log tag to %s", tag);
40 | TAG = tag;
41 |
42 | // Reinitialize the DEBUG "constant"
43 | DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
44 | }
45 |
46 | public static void v(String format, Object... args) {
47 | if (DEBUG) {
48 | Log.v(TAG, buildMessage(format, args));
49 | }
50 | }
51 |
52 | public static void d(String format, Object... args) {
53 | Log.d(TAG, buildMessage(format, args));
54 | }
55 |
56 | public static void e(String format, Object... args) {
57 | Log.e(TAG, buildMessage(format, args));
58 | }
59 |
60 | public static void e(Throwable tr, String format, Object... args) {
61 | Log.e(TAG, buildMessage(format, args), tr);
62 | }
63 |
64 | public static void wtf(String format, Object... args) {
65 | Log.wtf(TAG, buildMessage(format, args));
66 | }
67 |
68 | public static void wtf(Throwable tr, String format, Object... args) {
69 | Log.wtf(TAG, buildMessage(format, args), tr);
70 | }
71 |
72 | /**
73 | * Formats the caller's provided message and prepends useful info like
74 | * calling thread ID and method name.
75 | */
76 | private static String buildMessage(String format, Object... args) {
77 | String msg = (args == null) ? format : String.format(Locale.US, format, args);
78 | StackTraceElement[] trace = new Throwable().fillInStackTrace().getStackTrace();
79 |
80 | String caller = "";
81 | // Walk up the stack looking for the first caller outside of VolleyLog.
82 | // It will be at least two frames up, so start there.
83 | for (int i = 2; i < trace.length; i++) {
84 | Class> clazz = trace[i].getClass();
85 | if (!clazz.equals(VolleyLog.class)) {
86 | String callingClass = trace[i].getClassName();
87 | callingClass = callingClass.substring(callingClass.lastIndexOf('.') + 1);
88 | callingClass = callingClass.substring(callingClass.lastIndexOf('$') + 1);
89 |
90 | caller = callingClass + "." + trace[i].getMethodName();
91 | break;
92 | }
93 | }
94 | return String.format(Locale.US, "[%d] %s: %s",
95 | Thread.currentThread().getId(), caller, msg);
96 | }
97 |
98 | /**
99 | * A simple event log with records containing a name, thread ID, and timestamp.
100 | */
101 | static class MarkerLog {
102 | public static final boolean ENABLED = VolleyLog.DEBUG;
103 |
104 | /** Minimum duration from first marker to last in an marker log to warrant logging. */
105 | private static final long MIN_DURATION_FOR_LOGGING_MS = 0;
106 |
107 | private static class Marker {
108 | public final String name;
109 | public final long thread;
110 | public final long time;
111 |
112 | public Marker(String name, long thread, long time) {
113 | this.name = name;
114 | this.thread = thread;
115 | this.time = time;
116 | }
117 | }
118 |
119 | private final List mMarkers = new ArrayList();
120 | private boolean mFinished = false;
121 |
122 | /** Adds a marker to this log with the specified name. */
123 | public synchronized void add(String name, long threadId) {
124 | if (mFinished) {
125 | throw new IllegalStateException("Marker added to finished log");
126 | }
127 |
128 | mMarkers.add(new Marker(name, threadId, SystemClock.elapsedRealtime()));
129 | }
130 |
131 | /**
132 | * Closes the log, dumping it to logcat if the time difference between
133 | * the first and last markers is greater than {@link #MIN_DURATION_FOR_LOGGING_MS}.
134 | * @param header Header string to print above the marker log.
135 | */
136 | public synchronized void finish(String header) {
137 | mFinished = true;
138 |
139 | long duration = getTotalDuration();
140 | if (duration <= MIN_DURATION_FOR_LOGGING_MS) {
141 | return;
142 | }
143 |
144 | long prevTime = mMarkers.get(0).time;
145 | d("(%-4d ms) %s", duration, header);
146 | for (Marker marker : mMarkers) {
147 | long thisTime = marker.time;
148 | d("(+%-4d) [%2d] %s", (thisTime - prevTime), marker.thread, marker.name);
149 | prevTime = thisTime;
150 | }
151 | }
152 |
153 | @Override
154 | protected void finalize() throws Throwable {
155 | // Catch requests that have been collected (and hence end-of-lifed)
156 | // but had no debugging output printed for them.
157 | if (!mFinished) {
158 | finish("Request on the loose");
159 | e("Marker log finalized without finish() - uncaught exit point for request");
160 | }
161 | }
162 |
163 | /** Returns the time difference between the first and last events in this log. */
164 | private long getTotalDuration() {
165 | if (mMarkers.size() == 0) {
166 | return 0;
167 | }
168 |
169 | long first = mMarkers.get(0).time;
170 | long last = mMarkers.get(mMarkers.size() - 1).time;
171 | return last - first;
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/requestvolley/src/main/java/com/jude/http/RequestManager.java:
--------------------------------------------------------------------------------
1 | package com.jude.http;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 | import android.widget.ImageView;
6 |
7 | import com.jude.volley.AuthFailureError;
8 | import com.jude.volley.DefaultRetryPolicy;
9 | import com.jude.volley.Request;
10 | import com.jude.volley.Request.Method;
11 | import com.jude.volley.RequestQueue;
12 | import com.jude.volley.RetryPolicy;
13 | import com.jude.volley.VolleyError;
14 | import com.jude.volley.toolbox.ImageLoader;
15 | import com.jude.volley.toolbox.Volley;
16 |
17 | import java.io.UnsupportedEncodingException;
18 | import java.util.HashMap;
19 | import java.util.Map;
20 |
21 |
22 | public class RequestManager {
23 |
24 | private static final String CHARSET_UTF_8 = "UTF-8";
25 |
26 | private int TIMEOUT_COUNT = 10 * 1000;
27 |
28 | private int RETRY_TIMES = 1;
29 |
30 | private boolean SHOULD_CACHE = false;
31 |
32 | private HashMap HEADER;
33 |
34 | private volatile static RequestManager instance = null;
35 |
36 | private RequestQueue mRequestQueue = null;
37 |
38 | private NetworkImageCache mImageCache = null;
39 |
40 | private ImageLoader mImageLoader = null;
41 |
42 | private int times = 0;
43 |
44 | private boolean Debug = false;
45 | private String DebugTag;
46 |
47 |
48 | private RequestManager() {
49 |
50 | }
51 |
52 | public void setDebugMode(boolean isDebug,String DebugTag){
53 | this.Debug = isDebug;
54 | this.DebugTag = DebugTag;
55 | }
56 |
57 | public void init(Context context) {
58 | this.mRequestQueue = Volley.newRequestQueue(context);
59 | mImageCache = new NetworkImageCache(context);
60 | mImageLoader = new ImageLoader(RequestManager.getInstance()
61 | .getRequestQueue(), mImageCache);
62 | }
63 |
64 | public static RequestManager getInstance() {
65 | if (null == instance) {
66 | synchronized (RequestManager.class) {
67 | if (null == instance) {
68 | instance = new RequestManager();
69 | }
70 | }
71 | }
72 | return instance;
73 | }
74 |
75 | public RequestQueue getRequestQueue() {
76 | return this.mRequestQueue;
77 | }
78 |
79 | public void setTimeOut(int time){
80 | TIMEOUT_COUNT = time;
81 | }
82 |
83 | public void setRetryTimes(int times){
84 | RETRY_TIMES = times;
85 | }
86 |
87 | public void setCacheEnable(boolean isCache){
88 | SHOULD_CACHE = isCache;
89 | }
90 |
91 | public void setHeader(HashMap header){
92 | HEADER = header;
93 | }
94 |
95 | public LoadController get(String url, RequestListener requestListener) {
96 | return this.request(Method.GET, url, null , null, requestListener, SHOULD_CACHE, TIMEOUT_COUNT, RETRY_TIMES);
97 | }
98 |
99 | public LoadController get(String url, HashMap header,RequestListener requestListener, boolean shouldCache) {
100 | return this.request(Method.GET, url, null, header, requestListener, shouldCache, TIMEOUT_COUNT, RETRY_TIMES);
101 | }
102 |
103 |
104 | public LoadController post(final String url, Object data, final RequestListener requestListener) {
105 | return this.post(url, data, HEADER,requestListener, SHOULD_CACHE, TIMEOUT_COUNT, RETRY_TIMES);
106 | }
107 |
108 | public LoadController post(final String url, Object data, boolean checkCache,final RequestListener requestListener) {
109 | return this.post(url, data, HEADER,requestListener, checkCache, TIMEOUT_COUNT, RETRY_TIMES);
110 | }
111 |
112 | public void invalidate(String url, Object data){
113 | mRequestQueue.getCache().invalidate(url+data.toString(),true);
114 | }
115 |
116 |
117 | public LoadController post(final String url, Object data,HashMap header, final RequestListener requestListener, boolean shouldCache,
118 | int timeoutCount, int retryTimes) {
119 | return request(Method.POST, url, data,header, requestListener, shouldCache, timeoutCount, retryTimes);
120 | }
121 |
122 |
123 | public ImageLoader.ImageContainer img(final String url,final ImageView imageView){
124 | return mImageLoader.get(url, new ImageLoader.ImageListener() {
125 | @Override
126 | public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
127 | if (response.getBitmap()!=null){
128 | imageView.setImageBitmap(response.getBitmap());
129 | }
130 | }
131 |
132 | @Override
133 | public void onErrorResponse(VolleyError error) {
134 | }
135 | });
136 | }
137 |
138 | public ImageLoader.ImageContainer img(final String url,final ImageView imageView, final int resError){
139 | return mImageLoader.get(url, new ImageLoader.ImageListener() {
140 | @Override
141 | public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
142 | if (response.getBitmap()!=null){
143 | imageView.setImageBitmap(response.getBitmap());
144 | }
145 | }
146 |
147 | @Override
148 | public void onErrorResponse(VolleyError error) {
149 | imageView.setImageResource(resError);
150 | }
151 | });
152 | }
153 |
154 | public ImageLoader.ImageContainer img(final String url,final ImageLoader.ImageListener imageListener){
155 | return mImageLoader.get(url, imageListener);
156 | }
157 |
158 | public ImageLoader.ImageContainer img(final String url,final ImageLoader.ImageListener imageListener,int maxWidth,int maxHeight){
159 | return mImageLoader.get(url, imageListener, maxWidth, maxHeight);
160 | }
161 |
162 |
163 | public LoadController request(int method, final String url, final Object data, final Map headers,
164 | final RequestListener requestListener, boolean shouldCache, int timeoutCount, int retryTimes) {
165 | final int curIndex = this.times++;
166 | return this.sendRequest(method, url, data, headers, new LoadListener() {
167 | @Override
168 | public void onStart() {
169 | if(requestListener!=null)
170 | requestListener.onRequest();
171 | if(Debug)Log.i(DebugTag, curIndex+"Times-Params:"+url+(data==null?"":data.toString()));
172 | }
173 |
174 | @Override
175 | public void onSuccess(byte[] data, String url) {
176 |
177 | String parsed = null;
178 | try {
179 | parsed = new String(data, CHARSET_UTF_8);
180 | } catch (UnsupportedEncodingException e) {
181 | e.printStackTrace();
182 | }
183 | if(Debug)Log.i(DebugTag, curIndex+"Times-Response:"+parsed);
184 | if(requestListener!=null)
185 | requestListener.onSuccess(parsed);
186 | }
187 |
188 | @Override
189 | public void onError(String errorMsg, String url) {
190 | if(Debug)Log.i(DebugTag, curIndex+"Times-Error:"+errorMsg);
191 | if(requestListener!=null)
192 | requestListener.onError(errorMsg);
193 | }
194 | }, shouldCache, timeoutCount, retryTimes);
195 | }
196 |
197 |
198 | public LoadController sendRequest(int method, final String url, Object data,final Map headers,
199 | final LoadListener requestListener, boolean shouldCache, int timeoutCount, int retryTimes) {
200 | if (requestListener == null)
201 | throw new NullPointerException();
202 |
203 | final ByteArrayLoadController loadControler = new ByteArrayLoadController(requestListener);
204 |
205 | Request> request = null;
206 | request = new ByteArrayRequest(method, url, data, headers,loadControler, loadControler);
207 | request.setShouldCache(shouldCache);
208 |
209 |
210 | if (headers != null && !headers.isEmpty()) {// add headers if not empty
211 | try {
212 | request.getHeaders().putAll(headers);
213 | } catch (AuthFailureError e) {
214 | e.printStackTrace();
215 | }
216 | }
217 |
218 | RetryPolicy retryPolicy = new DefaultRetryPolicy(timeoutCount, retryTimes, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
219 | request.setRetryPolicy(retryPolicy);
220 |
221 | loadControler.bindRequest(request);
222 |
223 | if (this.mRequestQueue == null)
224 | throw new NullPointerException();
225 | requestListener.onStart();
226 | this.mRequestQueue.add(request);
227 |
228 | return loadControler;
229 | }
230 |
231 |
232 |
233 |
234 | }
235 |
--------------------------------------------------------------------------------
/requestvolley/src/main/java/com/jude/volley/toolbox/HttpClientStack.java:
--------------------------------------------------------------------------------
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 com.jude.volley.toolbox;
18 |
19 | import com.jude.volley.AuthFailureError;
20 | import com.jude.volley.Request;
21 | import com.jude.volley.Request.Method;
22 |
23 | import org.apache.http.HttpEntity;
24 | import org.apache.http.HttpResponse;
25 | import org.apache.http.NameValuePair;
26 | import org.apache.http.client.HttpClient;
27 | import org.apache.http.client.methods.HttpDelete;
28 | import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
29 | import org.apache.http.client.methods.HttpGet;
30 | import org.apache.http.client.methods.HttpHead;
31 | import org.apache.http.client.methods.HttpOptions;
32 | import org.apache.http.client.methods.HttpPost;
33 | import org.apache.http.client.methods.HttpPut;
34 | import org.apache.http.client.methods.HttpTrace;
35 | import org.apache.http.client.methods.HttpUriRequest;
36 | import org.apache.http.entity.ByteArrayEntity;
37 | import org.apache.http.message.BasicNameValuePair;
38 | import org.apache.http.params.HttpConnectionParams;
39 | import org.apache.http.params.HttpParams;
40 |
41 | import java.io.IOException;
42 | import java.net.URI;
43 | import java.util.ArrayList;
44 | import java.util.List;
45 | import java.util.Map;
46 |
47 | /**
48 | * An HttpStack that performs request over an {@link HttpClient}.
49 | */
50 | public class HttpClientStack implements HttpStack {
51 | protected final HttpClient mClient;
52 |
53 | private final static String HEADER_CONTENT_TYPE = "Content-Type";
54 |
55 | public HttpClientStack(HttpClient client) {
56 | mClient = client;
57 | }
58 |
59 | private static void addHeaders(HttpUriRequest httpRequest, Map headers) {
60 | for (String key : headers.keySet()) {
61 | httpRequest.setHeader(key, headers.get(key));
62 | }
63 | }
64 |
65 | @SuppressWarnings("unused")
66 | private static List getPostParameterPairs(Map postParams) {
67 | List result = new ArrayList(postParams.size());
68 | for (String key : postParams.keySet()) {
69 | result.add(new BasicNameValuePair(key, postParams.get(key)));
70 | }
71 | return result;
72 | }
73 |
74 | @Override
75 | public HttpResponse performRequest(Request> request, Map additionalHeaders)
76 | throws IOException, AuthFailureError {
77 | HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
78 | addHeaders(httpRequest, additionalHeaders);
79 | addHeaders(httpRequest, request.getHeaders());
80 | onPrepareRequest(httpRequest);
81 | HttpParams httpParams = httpRequest.getParams();
82 | int timeoutMs = request.getTimeoutMs();
83 | // TODO: Reevaluate this connection timeout based on more wide-scale
84 | // data collection and possibly different for wifi vs. 3G.
85 | HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
86 | HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
87 | return mClient.execute(httpRequest);
88 | }
89 |
90 | /**
91 | * Creates the appropriate subclass of HttpUriRequest for passed in request.
92 | */
93 | @SuppressWarnings("deprecation")
94 | /* protected */ static HttpUriRequest createHttpRequest(Request> request,
95 | Map additionalHeaders) throws AuthFailureError {
96 | switch (request.getMethod()) {
97 | case Method.DEPRECATED_GET_OR_POST: {
98 | // This is the deprecated way that needs to be handled for backwards compatibility.
99 | // If the request's post body is null, then the assumption is that the request is
100 | // GET. Otherwise, it is assumed that the request is a POST.
101 | byte[] postBody = request.getPostBody();
102 | if (postBody != null) {
103 | HttpPost postRequest = new HttpPost(request.getUrl());
104 | postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType());
105 | HttpEntity entity;
106 | entity = new ByteArrayEntity(postBody);
107 | postRequest.setEntity(entity);
108 | return postRequest;
109 | } else {
110 | return new HttpGet(request.getUrl());
111 | }
112 | }
113 | case Method.GET:
114 | return new HttpGet(request.getUrl());
115 | case Method.DELETE:
116 | return new HttpDelete(request.getUrl());
117 | case Method.POST: {
118 | HttpPost postRequest = new HttpPost(request.getUrl());
119 | postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
120 | setEntityIfNonEmptyBody(postRequest, request);
121 | return postRequest;
122 | }
123 | case Method.PUT: {
124 | HttpPut putRequest = new HttpPut(request.getUrl());
125 | putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
126 | setEntityIfNonEmptyBody(putRequest, request);
127 | return putRequest;
128 | }
129 | case Method.HEAD:
130 | return new HttpHead(request.getUrl());
131 | case Method.OPTIONS:
132 | return new HttpOptions(request.getUrl());
133 | case Method.TRACE:
134 | return new HttpTrace(request.getUrl());
135 | case Method.PATCH: {
136 | HttpPatch patchRequest = new HttpPatch(request.getUrl());
137 | patchRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
138 | setEntityIfNonEmptyBody(patchRequest, request);
139 | return patchRequest;
140 | }
141 | default:
142 | throw new IllegalStateException("Unknown request method.");
143 | }
144 | }
145 |
146 | private static void setEntityIfNonEmptyBody(HttpEntityEnclosingRequestBase httpRequest,
147 | Request> request) throws AuthFailureError {
148 | byte[] body = request.getBody();
149 | if (body != null) {
150 | HttpEntity entity = new ByteArrayEntity(body);
151 | httpRequest.setEntity(entity);
152 | }
153 | }
154 |
155 | /**
156 | * Called before the request is executed using the underlying HttpClient.
157 | *
158 | * Overwrite in subclasses to augment the request.
159 | */
160 | protected void onPrepareRequest(HttpUriRequest request) throws IOException {
161 | // Nothing.
162 | }
163 |
164 | /**
165 | * The HttpPatch class does not exist in the Android framework, so this has been defined here.
166 | */
167 | public static final class HttpPatch extends HttpEntityEnclosingRequestBase {
168 |
169 | public final static String METHOD_NAME = "PATCH";
170 |
171 | public HttpPatch() {
172 | super();
173 | }
174 |
175 | public HttpPatch(final URI uri) {
176 | super();
177 | setURI(uri);
178 | }
179 |
180 | /**
181 | * @throws IllegalArgumentException if the uri is invalid.
182 | */
183 | public HttpPatch(final String uri) {
184 | super();
185 | setURI(URI.create(uri));
186 | }
187 |
188 | @Override
189 | public String getMethod() {
190 | return METHOD_NAME;
191 | }
192 |
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/requestvolley/src/main/java/com/jude/volley/toolbox/NetworkImageView.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013 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 | package com.jude.volley.toolbox;
17 |
18 | import android.content.Context;
19 | import android.graphics.drawable.Drawable;
20 | import android.text.TextUtils;
21 | import android.util.AttributeSet;
22 | import android.view.ViewGroup.LayoutParams;
23 | import android.widget.ImageView;
24 |
25 | import com.jude.volley.VolleyError;
26 |
27 | /**
28 | * Handles fetching an image from a URL as well as the life-cycle of the
29 | * associated request.
30 | */
31 | public class NetworkImageView extends ImageView {
32 | /** The URL of the network image to load */
33 | private String mUrl;
34 |
35 | /**
36 | * Resource ID of the image to be used as a placeholder until the network
37 | * image is loaded.
38 | */
39 | private int mDefaultImageId;
40 |
41 | /**
42 | * Resource ID of the image to be used if the network response fails.
43 | */
44 | private int mErrorImageId;
45 |
46 | /**
47 | * Resource ID of the image to be used if the network response fails.
48 | */
49 | private Drawable mErrorImageDrawable;
50 |
51 | /** Local copy of the ImageLoader. */
52 | private ImageLoader mImageLoader;
53 |
54 | /** Current ImageContainer. (either in-flight or finished) */
55 | private ImageLoader.ImageContainer mImageContainer;
56 |
57 | public NetworkImageView(Context context) {
58 | this(context, null);
59 | }
60 |
61 | public NetworkImageView(Context context, AttributeSet attrs) {
62 | this(context, attrs, 0);
63 | }
64 |
65 | public NetworkImageView(Context context, AttributeSet attrs, int defStyle) {
66 | super(context, attrs, defStyle);
67 | }
68 |
69 | /**
70 | * Sets URL of the image that should be loaded into this view. Note that
71 | * calling this will immediately either set the cached image (if available)
72 | * or the default image specified by
73 | * {@link NetworkImageView#setDefaultImageResId(int)} on the view.
74 | *
75 | * NOTE: If applicable, {@link NetworkImageView#setDefaultImageResId(int)}
76 | * and {@link NetworkImageView#setErrorImageResId(int)} should be called
77 | * prior to calling this function.
78 | *
79 | * @param url
80 | * The URL that should be loaded into this ImageView.
81 | * @param imageLoader
82 | * ImageLoader that will be used to make the request.
83 | */
84 | public void setImageUrl(String url, ImageLoader imageLoader) {
85 | mUrl = url;
86 | mImageLoader = imageLoader;
87 | // The URL has potentially changed. See if we need to load it.
88 | loadImageIfNecessary(false);
89 | }
90 |
91 | /**
92 | * Sets the default image resource ID to be used for this view until the
93 | * attempt to load it completes.
94 | */
95 | public void setDefaultImageResId(int defaultImage) {
96 | mDefaultImageId = defaultImage;
97 | }
98 |
99 | /**
100 | * Sets the default image resource Drawable to be used for this view until
101 | * the attempt to load it completes.
102 | */
103 | public void setDefaultImageDrawable(Drawable imageDrawable) {
104 | mErrorImageDrawable = imageDrawable;
105 | }
106 |
107 | /**
108 | * Sets the error image resource ID to be used for this view in the event
109 | * that the image requested fails to load.
110 | */
111 | public void setErrorImageResId(int errorImage) {
112 | mErrorImageId = errorImage;
113 | }
114 |
115 | /**
116 | * Loads the image for the view if it isn't already loaded.
117 | *
118 | * @param isInLayoutPass
119 | * True if this was invoked from a layout pass, false otherwise.
120 | */
121 | void loadImageIfNecessary(final boolean isInLayoutPass) {
122 | int width = getWidth();
123 | int height = getHeight();
124 |
125 | boolean wrapWidth = false, wrapHeight = false;
126 | if (getLayoutParams() != null) {
127 | wrapWidth = getLayoutParams().width == LayoutParams.WRAP_CONTENT;
128 | wrapHeight = getLayoutParams().height == LayoutParams.WRAP_CONTENT;
129 | }
130 |
131 | // if the view's bounds aren't known yet, and this is not a
132 | // wrap-content/wrap-content
133 | // view, hold off on loading the image.
134 | boolean isFullyWrapContent = wrapWidth && wrapHeight;
135 | if (width == 0 && height == 0 && !isFullyWrapContent) {
136 | return;
137 | }
138 |
139 | // if the URL to be loaded in this view is empty, cancel any old
140 | // requests and clear the
141 | // currently loaded image.
142 | if (TextUtils.isEmpty(mUrl)) {
143 | if (mImageContainer != null) {
144 | mImageContainer.cancelRequest();
145 | mImageContainer = null;
146 | }
147 | setDefaultImageOrNull();
148 | return;
149 | }
150 |
151 | // if there was an old request in this view, check if it needs to be
152 | // canceled.
153 | if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
154 | if (mImageContainer.getRequestUrl().equals(mUrl)) {
155 | // if the request is from the same URL, return.
156 | return;
157 | } else {
158 | // if there is a pre-existing request, cancel it if it's
159 | // fetching a different URL.
160 | mImageContainer.cancelRequest();
161 | setDefaultImageOrNull();
162 | }
163 | }
164 |
165 | // Calculate the max image width / height to use while ignoring
166 | // WRAP_CONTENT dimens.
167 | int maxWidth = wrapWidth ? 0 : width;
168 | int maxHeight = wrapHeight ? 0 : height;
169 |
170 | // The pre-existing content of this view didn't match the current URL.
171 | // Load the new image
172 | // from the network.
173 | ImageLoader.ImageContainer newContainer = mImageLoader.get(mUrl,
174 | new ImageLoader.ImageListener() {
175 | @Override
176 | public void onErrorResponse(VolleyError error) {
177 | if (mErrorImageId != 0) {
178 | setImageResource(mErrorImageId);
179 | } else if (mErrorImageDrawable != null) {
180 | setImageDrawable(mErrorImageDrawable);
181 | }
182 | }
183 |
184 | @Override
185 | public void onResponse(final ImageLoader.ImageContainer response,
186 | boolean isImmediate) {
187 | // If this was an immediate response that was delivered
188 | // inside of a layout
189 | // pass do not set the image immediately as it will
190 | // trigger a requestLayout
191 | // inside of a layout. Instead, defer setting the image
192 | // by posting back to
193 | // the main thread.
194 | if (isImmediate && isInLayoutPass) {
195 | post(new Runnable() {
196 | @Override
197 | public void run() {
198 | onResponse(response, false);
199 | }
200 | });
201 | return;
202 | }
203 |
204 | if (response.getBitmap() != null) {
205 | setImageBitmap(response.getBitmap());
206 | } else if (mDefaultImageId != 0) {
207 | setImageResource(mDefaultImageId);
208 | } else if (mErrorImageDrawable != null) {
209 | setImageDrawable(mErrorImageDrawable);
210 | }
211 | }
212 | }, maxWidth, maxHeight);
213 |
214 | // update the ImageContainer to be the new bitmap container.
215 | mImageContainer = newContainer;
216 | }
217 |
218 | private void setDefaultImageOrNull() {
219 | if (mDefaultImageId != 0) {
220 | setImageResource(mDefaultImageId);
221 | } else if (mErrorImageDrawable != null) {
222 | setImageDrawable(mErrorImageDrawable);
223 | } else {
224 | setImageBitmap(null);
225 | }
226 | }
227 |
228 | @Override
229 | protected void onLayout(boolean changed, int left, int top, int right,
230 | int bottom) {
231 | super.onLayout(changed, left, top, right, bottom);
232 | loadImageIfNecessary(true);
233 | }
234 |
235 | @Override
236 | protected void onDetachedFromWindow() {
237 | if (mImageContainer != null) {
238 | // If the view was bound to an image request, cancel it and clear
239 | // out the image from the view.
240 | mImageContainer.cancelRequest();
241 | setImageBitmap(null);
242 | // also clear out the container so we can reload the image if
243 | // necessary.
244 | mImageContainer = null;
245 | }
246 | super.onDetachedFromWindow();
247 | }
248 |
249 | @Override
250 | protected void drawableStateChanged() {
251 | super.drawableStateChanged();
252 | invalidate();
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/requestvolley/src/main/java/com/jude/http/RequestMap.java:
--------------------------------------------------------------------------------
1 | package com.jude.http;
2 |
3 | import org.apache.http.Header;
4 | import org.apache.http.HttpEntity;
5 | import org.apache.http.client.entity.UrlEncodedFormEntity;
6 | import org.apache.http.message.BasicHeader;
7 | import org.apache.http.message.BasicNameValuePair;
8 |
9 | import java.io.ByteArrayInputStream;
10 | import java.io.ByteArrayOutputStream;
11 | import java.io.File;
12 | import java.io.FileInputStream;
13 | import java.io.FileNotFoundException;
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.io.OutputStream;
17 | import java.io.UnsupportedEncodingException;
18 | import java.util.AbstractMap;
19 | import java.util.ArrayList;
20 | import java.util.List;
21 | import java.util.Map;
22 | import java.util.Random;
23 |
24 |
25 | public class RequestMap {
26 | private static String ENCODING = "UTF-8";
27 |
28 | protected ArrayList> urlParams;
29 |
30 | protected ArrayList> fileParams;
31 |
32 | private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
33 | .toCharArray();
34 |
35 | public RequestMap() {
36 | init();
37 | }
38 |
39 | public RequestMap(String key, String value) {
40 | init();
41 | put(key, value);
42 | }
43 |
44 | public RequestMap(String key, File value) {
45 | init();
46 | put(key, value);
47 | }
48 |
49 | private void init() {
50 | urlParams = new ArrayList();
51 | fileParams = new ArrayList();
52 | }
53 |
54 |
55 | public void put(String key, String value) {
56 | if (key != null && value != null) {
57 | urlParams.add(new AbstractMap.SimpleEntry<>(key, value));
58 | }
59 | }
60 |
61 |
62 | public void put(String key, File file) {
63 | try {
64 | put(key, new FileInputStream(file), file.getName());
65 | } catch (FileNotFoundException e) {
66 | e.printStackTrace();
67 | }
68 | }
69 |
70 |
71 | public void put(String key, InputStream stream, String fileName) {
72 | put(key, stream, fileName, null);
73 | }
74 |
75 |
76 | public void put(String key, InputStream stream, String fileName, String contentType) {
77 | if (key != null && stream != null) {
78 | fileParams.add(new AbstractMap.SimpleEntry(key, new FileWrapper(stream, fileName, contentType)));
79 | }
80 | }
81 |
82 | @Override
83 | public String toString() {
84 | String params = "?";
85 | for (Map.Entry entry : urlParams) {
86 | if(!params.equals("?")){
87 | params+="&";
88 | }
89 | params+=entry.getKey()+"="+entry.getValue();
90 | }
91 | for (Map.Entry entry : fileParams) {
92 | if(!params.equals("?")){
93 | params+="&";
94 | }
95 | params+=entry.getKey()+"=File:{"+entry.getValue().fileName+"}";
96 | }
97 | return params;
98 | }
99 |
100 | public HttpEntity getEntity() {
101 | HttpEntity entity = null;
102 | if (!fileParams.isEmpty()) {
103 | MultipartEntity multipartEntity = new MultipartEntity();
104 | for (Map.Entry entry : urlParams) {// Add string params
105 | multipartEntity.addPart(entry.getKey(), entry.getValue());
106 | }
107 | int currentIndex = 0;
108 | int lastIndex = fileParams.size() - 1;
109 | for (Map.Entry entry : fileParams) {//Add file params
110 | FileWrapper file = entry.getValue();
111 | if (file.inputStream != null) {
112 | boolean isLast = currentIndex == lastIndex;
113 | if (file.contentType != null) {
114 | multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, file.contentType,
115 | isLast);
116 | } else {
117 | multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, isLast);
118 | }
119 | }
120 | currentIndex++;
121 | }
122 | entity = multipartEntity;
123 | } else {
124 | try {
125 | entity = new UrlEncodedFormEntity(getParamsList(), ENCODING);
126 | } catch (UnsupportedEncodingException e) {
127 | e.printStackTrace();
128 | }
129 | }
130 | return entity;
131 | }
132 |
133 | protected List getParamsList() {
134 | List lparams = new ArrayList();
135 | for (Map.Entry entry : urlParams) {
136 | lparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
137 | }
138 | return lparams;
139 | }
140 |
141 | private static class FileWrapper {
142 | public InputStream inputStream;
143 | public String fileName;
144 | public String contentType;
145 |
146 | public FileWrapper(InputStream inputStream, String fileName, String contentType) {
147 | this.inputStream = inputStream;
148 | this.fileName = fileName;
149 | this.contentType = contentType;
150 | }
151 |
152 | public String getFileName() {
153 | if (fileName != null) {
154 | return fileName;
155 | } else {
156 | return "nofilename";
157 | }
158 | }
159 | }
160 |
161 | class MultipartEntity implements HttpEntity {
162 | private String boundary = null;
163 |
164 | ByteArrayOutputStream out = new ByteArrayOutputStream();
165 |
166 | boolean isSetLast = false;
167 |
168 | boolean isSetFirst = false;
169 |
170 | public MultipartEntity() {
171 | final StringBuffer buf = new StringBuffer();
172 | final Random rand = new Random();
173 | for (int i = 0; i < 30; i++) {
174 | buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
175 | }
176 | this.boundary = buf.toString();
177 | }
178 |
179 | public void writeFirstBoundaryIfNeeds() {
180 | if (!isSetFirst) {
181 | try {
182 | out.write(("--" + boundary + "\r\n").getBytes());
183 | } catch (final IOException e) {
184 | e.printStackTrace();
185 | }
186 | }
187 | isSetFirst = true;
188 | }
189 |
190 | public void writeLastBoundaryIfNeeds() {
191 | if (isSetLast) {
192 | return;
193 | }
194 | try {
195 | out.write(("\r\n--" + boundary + "--\r\n").getBytes());
196 | } catch (final IOException e) {
197 | e.printStackTrace();
198 | }
199 | isSetLast = true;
200 | }
201 |
202 | public void addPart(final String key, final String value) {
203 | writeFirstBoundaryIfNeeds();
204 | try {
205 | out.write(("Content-Disposition: form-data; name=\"" + key + "\"\r\n\r\n").getBytes());
206 | out.write(value.getBytes());
207 | out.write(("\r\n--" + boundary + "\r\n").getBytes());
208 | } catch (final IOException e) {
209 | e.printStackTrace();
210 | }
211 | }
212 |
213 | public void addPart(final String key, final String fileName, final InputStream fin, final boolean isLast) {
214 | addPart(key, fileName, fin, "application/octet-stream", isLast);
215 | }
216 |
217 | public void addPart(final String key, final String fileName, final InputStream fin, String type, final boolean isLast) {
218 | writeFirstBoundaryIfNeeds();
219 | try {
220 | type = "Content-Type: " + type + "\r\n";
221 | out.write(("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + fileName + "\"\r\n")
222 | .getBytes());
223 | out.write(type.getBytes());
224 | out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes());
225 |
226 | final byte[] tmp = new byte[4096];
227 | int l = 0;
228 | while ((l = fin.read(tmp)) != -1) {
229 | out.write(tmp, 0, l);
230 | }
231 | if (!isLast)
232 | out.write(("\r\n--" + boundary + "\r\n").getBytes());
233 | else {
234 | writeLastBoundaryIfNeeds();
235 | }
236 | out.flush();
237 | } catch (final IOException e) {
238 | e.printStackTrace();
239 | } finally {
240 | try {
241 | fin.close();
242 | } catch (final IOException e) {
243 | e.printStackTrace();
244 | }
245 | }
246 | }
247 |
248 | public void addPart(final String key, final File value, final boolean isLast) {
249 | try {
250 | addPart(key, value.getName(), new FileInputStream(value), isLast);
251 | } catch (final FileNotFoundException e) {
252 | e.printStackTrace();
253 | }
254 | }
255 |
256 | @Override
257 | public long getContentLength() {
258 | writeLastBoundaryIfNeeds();
259 | return out.toByteArray().length;
260 | }
261 |
262 | @Override
263 | public Header getContentType() {
264 | return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
265 | }
266 |
267 | @Override
268 | public boolean isChunked() {
269 | return false;
270 | }
271 |
272 | @Override
273 | public boolean isRepeatable() {
274 | return false;
275 | }
276 |
277 | @Override
278 | public boolean isStreaming() {
279 | return false;
280 | }
281 |
282 | @Override
283 | public void writeTo(final OutputStream outstream) throws IOException {
284 | outstream.write(out.toByteArray());
285 | }
286 |
287 | @Override
288 | public Header getContentEncoding() {
289 | return null;
290 | }
291 |
292 | @Override
293 | public void consumeContent() throws IOException, UnsupportedOperationException {
294 | if (isStreaming()) {
295 | throw new UnsupportedOperationException("Streaming entity does not implement #consumeContent()");
296 | }
297 | }
298 |
299 | @Override
300 | public InputStream getContent() throws IOException, UnsupportedOperationException {
301 | return new ByteArrayInputStream(out.toByteArray());
302 | }
303 | }
304 |
305 | }
--------------------------------------------------------------------------------
/requestvolley/src/main/java/com/jude/volley/toolbox/ImageRequest.java:
--------------------------------------------------------------------------------
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 com.jude.volley.toolbox;
18 |
19 | import com.jude.volley.DefaultRetryPolicy;
20 | import com.jude.volley.NetworkResponse;
21 | import com.jude.volley.ParseError;
22 | import com.jude.volley.Request;
23 | import com.jude.volley.Response;
24 | import com.jude.volley.VolleyLog;
25 |
26 | import android.graphics.Bitmap;
27 | import android.graphics.Bitmap.Config;
28 | import android.graphics.BitmapFactory;
29 |
30 | /**
31 | * A canned request for getting an image at a given URL and calling
32 | * back with a decoded Bitmap.
33 | */
34 | public class ImageRequest extends Request {
35 | /** Socket timeout in milliseconds for image requests */
36 | private static final int IMAGE_TIMEOUT_MS = 1000;
37 |
38 | /** Default number of retries for image requests */
39 | private static final int IMAGE_MAX_RETRIES = 2;
40 |
41 | /** Default backoff multiplier for image requests */
42 | private static final float IMAGE_BACKOFF_MULT = 2f;
43 |
44 | private final Response.Listener mListener;
45 | private final Config mDecodeConfig;
46 | private final int mMaxWidth;
47 | private final int mMaxHeight;
48 |
49 | /** Decoding lock so that we don't decode more than one image at a time (to avoid OOM's) */
50 | private static final Object sDecodeLock = new Object();
51 |
52 | /**
53 | * Creates a new image request, decoding to a maximum specified width and
54 | * height. If both width and height are zero, the image will be decoded to
55 | * its natural size. If one of the two is nonzero, that dimension will be
56 | * clamped and the other one will be set to preserve the image's aspect
57 | * ratio. If both width and height are nonzero, the image will be decoded to
58 | * be fit in the rectangle of dimensions width x height while keeping its
59 | * aspect ratio.
60 | *
61 | * @param url URL of the image
62 | * @param listener Listener to receive the decoded bitmap
63 | * @param maxWidth Maximum width to decode this bitmap to, or zero for none
64 | * @param maxHeight Maximum height to decode this bitmap to, or zero for
65 | * none
66 | * @param decodeConfig Format to decode the bitmap to
67 | * @param errorListener Error listener, or null to ignore errors
68 | */
69 | public ImageRequest(String url, Response.Listener listener, int maxWidth, int maxHeight,
70 | Config decodeConfig, Response.ErrorListener errorListener) {
71 | super(Method.GET, url, errorListener);
72 | setRetryPolicy(
73 | new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
74 | mListener = listener;
75 | mDecodeConfig = decodeConfig;
76 | mMaxWidth = maxWidth;
77 | mMaxHeight = maxHeight;
78 | }
79 |
80 | @Override
81 | public Priority getPriority() {
82 | return Priority.LOW;
83 | }
84 |
85 | /**
86 | * Scales one side of a rectangle to fit aspect ratio.
87 | *
88 | * @param maxPrimary Maximum size of the primary dimension (i.e. width for
89 | * max width), or zero to maintain aspect ratio with secondary
90 | * dimension
91 | * @param maxSecondary Maximum size of the secondary dimension, or zero to
92 | * maintain aspect ratio with primary dimension
93 | * @param actualPrimary Actual size of the primary dimension
94 | * @param actualSecondary Actual size of the secondary dimension
95 | */
96 | private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
97 | int actualSecondary) {
98 | // If no dominant value at all, just return the actual.
99 | if (maxPrimary == 0 && maxSecondary == 0) {
100 | return actualPrimary;
101 | }
102 |
103 | // If primary is unspecified, scale primary to match secondary's scaling ratio.
104 | if (maxPrimary == 0) {
105 | double ratio = (double) maxSecondary / (double) actualSecondary;
106 | return (int) (actualPrimary * ratio);
107 | }
108 |
109 | if (maxSecondary == 0) {
110 | return maxPrimary;
111 | }
112 |
113 | double ratio = (double) actualSecondary / (double) actualPrimary;
114 | int resized = maxPrimary;
115 | if (resized * ratio > maxSecondary) {
116 | resized = (int) (maxSecondary / ratio);
117 | }
118 | return resized;
119 | }
120 |
121 | @Override
122 | protected Response parseNetworkResponse(NetworkResponse response) {
123 | // Serialize all decode on a global lock to reduce concurrent heap usage.
124 | synchronized (sDecodeLock) {
125 | try {
126 | return doParse(response);
127 | } catch (OutOfMemoryError e) {
128 | VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
129 | return Response.error(new ParseError(e));
130 | }
131 | }
132 | }
133 |
134 | /**
135 | * The real guts of parseNetworkResponse. Broken out for readability.
136 | */
137 | private Response doParse(NetworkResponse response) {
138 | byte[] data = response.data;
139 | BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
140 | Bitmap bitmap = null;
141 | if (mMaxWidth == 0 && mMaxHeight == 0) {
142 | decodeOptions.inPreferredConfig = mDecodeConfig;
143 | bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
144 | } else {
145 | // If we have to resize this image, first get the natural bounds.
146 | decodeOptions.inJustDecodeBounds = true;
147 | BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
148 | int actualWidth = decodeOptions.outWidth;
149 | int actualHeight = decodeOptions.outHeight;
150 |
151 | // Then compute the dimensions we would ideally like to decode to.
152 | int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
153 | actualWidth, actualHeight);
154 | int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
155 | actualHeight, actualWidth);
156 |
157 | // Decode to the nearest power of two scaling factor.
158 | decodeOptions.inJustDecodeBounds = false;
159 | // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
160 | // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
161 | decodeOptions.inSampleSize =
162 | findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
163 | Bitmap tempBitmap =
164 | BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
165 |
166 | // If necessary, scale down to the maximal acceptable size.
167 | if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
168 | tempBitmap.getHeight() > desiredHeight)) {
169 | bitmap = Bitmap.createScaledBitmap(tempBitmap,
170 | desiredWidth, desiredHeight, true);
171 | tempBitmap.recycle();
172 | } else {
173 | bitmap = tempBitmap;
174 | }
175 | }
176 |
177 | if (bitmap == null) {
178 | return Response.error(new ParseError(response));
179 | } else {
180 | return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
181 | }
182 | }
183 |
184 | @Override
185 | protected void deliverResponse(Bitmap response) {
186 | mListener.onResponse(response);
187 | }
188 |
189 | /**
190 | * Returns the largest power-of-two divisor for use in downscaling a bitmap
191 | * that will not result in the scaling past the desired dimensions.
192 | *
193 | * @param actualWidth Actual width of the bitmap
194 | * @param actualHeight Actual height of the bitmap
195 | * @param desiredWidth Desired width of the bitmap
196 | * @param desiredHeight Desired height of the bitmap
197 | */
198 | // Visible for testing.
199 | static int findBestSampleSize(
200 | int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
201 | double wr = (double) actualWidth / desiredWidth;
202 | double hr = (double) actualHeight / desiredHeight;
203 | double ratio = Math.min(wr, hr);
204 | float n = 1.0f;
205 | while ((n * 2) <= ratio) {
206 | n *= 2;
207 | }
208 |
209 | return (int) n;
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/requestvolley/src/main/java/com/jude/volley/toolbox/HurlStack.java:
--------------------------------------------------------------------------------
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 com.jude.volley.toolbox;
18 |
19 | import com.jude.volley.AuthFailureError;
20 | import com.jude.volley.Request;
21 | import com.jude.volley.Request.Method;
22 |
23 | import org.apache.http.Header;
24 | import org.apache.http.HttpEntity;
25 | import org.apache.http.HttpResponse;
26 | import org.apache.http.ProtocolVersion;
27 | import org.apache.http.StatusLine;
28 | import org.apache.http.entity.BasicHttpEntity;
29 | import org.apache.http.message.BasicHeader;
30 | import org.apache.http.message.BasicHttpResponse;
31 | import org.apache.http.message.BasicStatusLine;
32 |
33 | import java.io.DataOutputStream;
34 | import java.io.IOException;
35 | import java.io.InputStream;
36 | import java.net.HttpURLConnection;
37 | import java.net.URL;
38 | import java.util.HashMap;
39 | import java.util.List;
40 | import java.util.Map;
41 | import java.util.Map.Entry;
42 |
43 | import javax.net.ssl.HttpsURLConnection;
44 | import javax.net.ssl.SSLSocketFactory;
45 |
46 | /**
47 | * An {@link HttpStack} based on {@link HttpURLConnection}.
48 | */
49 | public class HurlStack implements HttpStack {
50 |
51 | private static final String HEADER_CONTENT_TYPE = "Content-Type";
52 |
53 | /**
54 | * An interface for transforming URLs before use.
55 | */
56 | public interface UrlRewriter {
57 | /**
58 | * Returns a URL to use instead of the provided one, or null to indicate
59 | * this URL should not be used at all.
60 | */
61 | public String rewriteUrl(String originalUrl);
62 | }
63 |
64 | private final UrlRewriter mUrlRewriter;
65 | private final SSLSocketFactory mSslSocketFactory;
66 |
67 | public HurlStack() {
68 | this(null);
69 | }
70 |
71 | /**
72 | * @param urlRewriter Rewriter to use for request URLs
73 | */
74 | public HurlStack(UrlRewriter urlRewriter) {
75 | this(urlRewriter, null);
76 | }
77 |
78 | /**
79 | * @param urlRewriter Rewriter to use for request URLs
80 | * @param sslSocketFactory SSL factory to use for HTTPS connections
81 | */
82 | public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
83 | mUrlRewriter = urlRewriter;
84 | mSslSocketFactory = sslSocketFactory;
85 | }
86 |
87 | @Override
88 | public HttpResponse performRequest(Request> request, Map additionalHeaders)
89 | throws IOException, AuthFailureError {
90 | String url = request.getUrl();
91 | HashMap map = new HashMap();
92 | map.putAll(request.getHeaders());
93 | map.putAll(additionalHeaders);
94 | if (mUrlRewriter != null) {
95 | String rewritten = mUrlRewriter.rewriteUrl(url);
96 | if (rewritten == null) {
97 | throw new IOException("URL blocked by rewriter: " + url);
98 | }
99 | url = rewritten;
100 | }
101 | URL parsedUrl = new URL(url);
102 | HttpURLConnection connection = openConnection(parsedUrl, request);
103 | for (String headerName : map.keySet()) {
104 | connection.addRequestProperty(headerName, map.get(headerName));
105 | }
106 | setConnectionParametersForRequest(connection, request);
107 | // Initialize HttpResponse with data from the HttpURLConnection.
108 | ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
109 | int responseCode = connection.getResponseCode();
110 | if (responseCode == -1) {
111 | // -1 is returned by getResponseCode() if the response code could not be retrieved.
112 | // Signal to the caller that something was wrong with the connection.
113 | throw new IOException("Could not retrieve response code from HttpUrlConnection.");
114 | }
115 | StatusLine responseStatus = new BasicStatusLine(protocolVersion,
116 | connection.getResponseCode(), connection.getResponseMessage());
117 | BasicHttpResponse response = new BasicHttpResponse(responseStatus);
118 | response.setEntity(entityFromConnection(connection));
119 | for (Entry> header : connection.getHeaderFields().entrySet()) {
120 | if (header.getKey() != null) {
121 | Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
122 | response.addHeader(h);
123 | }
124 | }
125 | return response;
126 | }
127 |
128 | /**
129 | * Initializes an {@link HttpEntity} from the given {@link HttpURLConnection}.
130 | * @param connection
131 | * @return an HttpEntity populated with data from connection.
132 | */
133 | private static HttpEntity entityFromConnection(HttpURLConnection connection) {
134 | BasicHttpEntity entity = new BasicHttpEntity();
135 | InputStream inputStream;
136 | try {
137 | inputStream = connection.getInputStream();
138 | } catch (IOException ioe) {
139 | inputStream = connection.getErrorStream();
140 | }
141 | entity.setContent(inputStream);
142 | entity.setContentLength(connection.getContentLength());
143 | entity.setContentEncoding(connection.getContentEncoding());
144 | entity.setContentType(connection.getContentType());
145 | return entity;
146 | }
147 |
148 | /**
149 | * Create an {@link HttpURLConnection} for the specified {@code url}.
150 | */
151 | protected HttpURLConnection createConnection(URL url) throws IOException {
152 | return (HttpURLConnection) url.openConnection();
153 | }
154 |
155 | /**
156 | * Opens an {@link HttpURLConnection} with parameters.
157 | * @param url
158 | * @return an open connection
159 | * @throws IOException
160 | */
161 | private HttpURLConnection openConnection(URL url, Request> request) throws IOException {
162 | HttpURLConnection connection = createConnection(url);
163 |
164 | int timeoutMs = request.getTimeoutMs();
165 | connection.setConnectTimeout(timeoutMs);
166 | connection.setReadTimeout(timeoutMs);
167 | connection.setUseCaches(false);
168 | connection.setDoInput(true);
169 |
170 | // use caller-provided custom SslSocketFactory, if any, for HTTPS
171 | if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
172 | ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);
173 | }
174 |
175 | return connection;
176 | }
177 |
178 | @SuppressWarnings("deprecation")
179 | /* package */ static void setConnectionParametersForRequest(HttpURLConnection connection,
180 | Request> request) throws IOException, AuthFailureError {
181 | switch (request.getMethod()) {
182 | case Method.DEPRECATED_GET_OR_POST:
183 | // This is the deprecated way that needs to be handled for backwards compatibility.
184 | // If the request's post body is null, then the assumption is that the request is
185 | // GET. Otherwise, it is assumed that the request is a POST.
186 | byte[] postBody = request.getPostBody();
187 | if (postBody != null) {
188 | // Prepare output. There is no need to set Content-Length explicitly,
189 | // since this is handled by HttpURLConnection using the size of the prepared
190 | // output stream.
191 | connection.setDoOutput(true);
192 | connection.setRequestMethod("POST");
193 | connection.addRequestProperty(HEADER_CONTENT_TYPE,
194 | request.getPostBodyContentType());
195 | DataOutputStream out = new DataOutputStream(connection.getOutputStream());
196 | out.write(postBody);
197 | out.close();
198 | }
199 | break;
200 | case Method.GET:
201 | // Not necessary to set the request method because connection defaults to GET but
202 | // being explicit here.
203 | connection.setRequestMethod("GET");
204 | break;
205 | case Method.DELETE:
206 | connection.setRequestMethod("DELETE");
207 | break;
208 | case Method.POST:
209 | connection.setRequestMethod("POST");
210 | addBodyIfExists(connection, request);
211 | break;
212 | case Method.PUT:
213 | connection.setRequestMethod("PUT");
214 | addBodyIfExists(connection, request);
215 | break;
216 | case Method.HEAD:
217 | connection.setRequestMethod("HEAD");
218 | break;
219 | case Method.OPTIONS:
220 | connection.setRequestMethod("OPTIONS");
221 | break;
222 | case Method.TRACE:
223 | connection.setRequestMethod("TRACE");
224 | break;
225 | case Method.PATCH:
226 | addBodyIfExists(connection, request);
227 | connection.setRequestMethod("PATCH");
228 | break;
229 | default:
230 | throw new IllegalStateException("Unknown method type.");
231 | }
232 | }
233 |
234 | private static void addBodyIfExists(HttpURLConnection connection, Request> request)
235 | throws IOException, AuthFailureError {
236 | byte[] body = request.getBody();
237 | if (body != null) {
238 | connection.setDoOutput(true);
239 | connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
240 | DataOutputStream out = new DataOutputStream(connection.getOutputStream());
241 | out.write(body);
242 | out.close();
243 | }
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/requestvolley/src/main/java/com/jude/volley/RequestQueue.java:
--------------------------------------------------------------------------------
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 com.jude.volley;
18 |
19 | import android.os.Handler;
20 | import android.os.Looper;
21 | import android.util.Log;
22 |
23 | import java.util.HashMap;
24 | import java.util.HashSet;
25 | import java.util.LinkedList;
26 | import java.util.Map;
27 | import java.util.Queue;
28 | import java.util.Set;
29 | import java.util.concurrent.PriorityBlockingQueue;
30 | import java.util.concurrent.atomic.AtomicInteger;
31 |
32 | /**
33 | * A request dispatch queue with a thread pool of dispatchers.
34 | *
35 | * Calling {@link #add(Request)} will enqueue the given Request for dispatch,
36 | * resolving from either cache or network on a worker thread, and then delivering
37 | * a parsed response on the main thread.
38 | */
39 | public class RequestQueue {
40 |
41 | /** Used for generating monotonically-increasing sequence numbers for requests. */
42 | private AtomicInteger mSequenceGenerator = new AtomicInteger();
43 |
44 | /**
45 | * Staging area for requests that already have a duplicate request in flight.
46 | *
47 | *
48 | * - containsKey(cacheKey) indicates that there is a request in flight for the given cache
49 | * key.
50 | * - get(cacheKey) returns waiting requests for the given cache key. The in flight request
51 | * is not contained in that list. Is null if no requests are staged.
52 | *
53 | */
54 | private final Map>> mWaitingRequests =
55 | new HashMap>>();
56 |
57 | /**
58 | * The set of all requests currently being processed by this RequestQueue. A Request
59 | * will be in this set if it is waiting in any queue or currently being processed by
60 | * any dispatcher.
61 | */
62 | private final Set> mCurrentRequests = new HashSet>();
63 |
64 | /** The cache triage queue. */
65 | private final PriorityBlockingQueue> mCacheQueue =
66 | new PriorityBlockingQueue>();
67 |
68 | /** The queue of requests that are actually going out to the network. */
69 | private final PriorityBlockingQueue> mNetworkQueue =
70 | new PriorityBlockingQueue>();
71 |
72 | /** Number of network request dispatcher threads to start. */
73 | private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
74 |
75 | /** Cache interface for retrieving and storing responses. */
76 | private final Cache mCache;
77 |
78 | /** Network interface for performing requests. */
79 | private final Network mNetwork;
80 |
81 | /** Response delivery mechanism. */
82 | private final ResponseDelivery mDelivery;
83 |
84 | /** The network dispatchers. */
85 | private NetworkDispatcher[] mDispatchers;
86 |
87 | /** The cache dispatcher. */
88 | private CacheDispatcher mCacheDispatcher;
89 |
90 | /**
91 | * Creates the worker pool. Processing will not begin until {@link #start()} is called.
92 | *
93 | * @param cache A Cache to use for persisting responses to disk
94 | * @param network A Network interface for performing HTTP requests
95 | * @param threadPoolSize Number of network dispatcher threads to create
96 | * @param delivery A ResponseDelivery interface for posting responses and errors
97 | */
98 | public RequestQueue(Cache cache, Network network, int threadPoolSize,
99 | ResponseDelivery delivery) {
100 | mCache = cache;
101 | mNetwork = network;
102 | mDispatchers = new NetworkDispatcher[threadPoolSize];
103 | mDelivery = delivery;
104 | }
105 |
106 | /**
107 | * Creates the worker pool. Processing will not begin until {@link #start()} is called.
108 | *
109 | * @param cache A Cache to use for persisting responses to disk
110 | * @param network A Network interface for performing HTTP requests
111 | * @param threadPoolSize Number of network dispatcher threads to create
112 | */
113 | public RequestQueue(Cache cache, Network network, int threadPoolSize) {
114 | this(cache, network, threadPoolSize,
115 | new ExecutorDelivery(new Handler(Looper.getMainLooper())));
116 | }
117 |
118 | /**
119 | * Creates the worker pool. Processing will not begin until {@link #start()} is called.
120 | *
121 | * @param cache A Cache to use for persisting responses to disk
122 | * @param network A Network interface for performing HTTP requests
123 | */
124 | public RequestQueue(Cache cache, Network network) {
125 | this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
126 | }
127 |
128 | /**
129 | * Starts the dispatchers in this queue.
130 | */
131 | public void start() {
132 | stop(); // Make sure any currently running dispatchers are stopped.
133 | // Create the cache dispatcher and start it.
134 | mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
135 | mCacheDispatcher.start();
136 |
137 | // Create network dispatchers (and corresponding threads) up to the pool size.
138 | for (int i = 0; i < mDispatchers.length; i++) {
139 | NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
140 | mCache, mDelivery);
141 | mDispatchers[i] = networkDispatcher;
142 | networkDispatcher.start();
143 | }
144 | }
145 |
146 | /**
147 | * Stops the cache and network dispatchers.
148 | */
149 | public void stop() {
150 | if (mCacheDispatcher != null) {
151 | mCacheDispatcher.quit();
152 | }
153 | for (int i = 0; i < mDispatchers.length; i++) {
154 | if (mDispatchers[i] != null) {
155 | mDispatchers[i].quit();
156 | }
157 | }
158 | }
159 |
160 | /**
161 | * Gets a sequence number.
162 | */
163 | public int getSequenceNumber() {
164 | return mSequenceGenerator.incrementAndGet();
165 | }
166 |
167 | /**
168 | * Gets the {@link Cache} instance being used.
169 | */
170 | public Cache getCache() {
171 | return mCache;
172 | }
173 |
174 | /**
175 | * A simple predicate or filter interface for Requests, for use by
176 | * {@link RequestQueue#cancelAll(RequestFilter)}.
177 | */
178 | public interface RequestFilter {
179 | public boolean apply(Request> request);
180 | }
181 |
182 | /**
183 | * Cancels all requests in this queue for which the given filter applies.
184 | * @param filter The filtering function to use
185 | */
186 | public void cancelAll(RequestFilter filter) {
187 | synchronized (mCurrentRequests) {
188 | for (Request> request : mCurrentRequests) {
189 | if (filter.apply(request)) {
190 | request.cancel();
191 | }
192 | }
193 | }
194 | }
195 |
196 | /**
197 | * Cancels all requests in this queue with the given tag. Tag must be non-null
198 | * and equality is by identity.
199 | */
200 | public void cancelAll(final Object tag) {
201 | if (tag == null) {
202 | throw new IllegalArgumentException("Cannot cancelAll with a null tag");
203 | }
204 | cancelAll(new RequestFilter() {
205 | @Override
206 | public boolean apply(Request> request) {
207 | return request.getTag() == tag;
208 | }
209 | });
210 | }
211 |
212 | /**
213 | * Adds a Request to the dispatch queue.
214 | * @param request The request to service
215 | * @return The passed-in request
216 | */
217 | public Request add(Request request) {
218 | // Tag the request as belonging to this queue and add it to the set of current requests.
219 | request.setRequestQueue(this);
220 | synchronized (mCurrentRequests) {
221 | mCurrentRequests.add(request);
222 | }
223 |
224 | // Process requests in the order they are added.
225 | request.setSequence(getSequenceNumber());
226 | request.addMarker("add-to-queue");
227 |
228 | // If the request is uncacheable, skip the cache queue and go straight to the network.
229 | if (!request.shouldCache()) {
230 | Log.i("JobNet","Shouldn't cache");
231 | mNetworkQueue.add(request);
232 | return request;
233 | }
234 |
235 | // Insert request into stage if there's already a request with the same cache key in flight.
236 | synchronized (mWaitingRequests) {
237 | String cacheKey = request.getCacheKey();
238 | if (mWaitingRequests.containsKey(cacheKey)) {
239 | // There is already a request in flight. Queue up.
240 | Queue> stagedRequests = mWaitingRequests.get(cacheKey);
241 | if (stagedRequests == null) {
242 | stagedRequests = new LinkedList>();
243 | }
244 | stagedRequests.add(request);
245 | mWaitingRequests.put(cacheKey, stagedRequests);
246 | if (VolleyLog.DEBUG) {
247 | VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
248 | }
249 | } else {
250 | // Insert 'null' queue for this cacheKey, indicating there is now a request in
251 | // flight.
252 | mWaitingRequests.put(cacheKey, null);
253 | mCacheQueue.add(request);
254 | }
255 | return request;
256 | }
257 | }
258 |
259 | /**
260 | * Called from {@link Request#finish(String)}, indicating that processing of the given request
261 | * has finished.
262 | *
263 | * Releases waiting requests for request.getCacheKey() if
264 | * request.shouldCache().
265 | */
266 | void finish(Request> request) {
267 | // Remove from the set of requests currently being processed.
268 | synchronized (mCurrentRequests) {
269 | mCurrentRequests.remove(request);
270 | }
271 |
272 | if (request.shouldCache()) {
273 | synchronized (mWaitingRequests) {
274 | String cacheKey = request.getCacheKey();
275 | Queue> waitingRequests = mWaitingRequests.remove(cacheKey);
276 | if (waitingRequests != null) {
277 | if (VolleyLog.DEBUG) {
278 | VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
279 | waitingRequests.size(), cacheKey);
280 | }
281 | // Process all queued up requests. They won't be considered as in flight, but
282 | // that's not a problem as the cache has been primed by 'request'.
283 | mCacheQueue.addAll(waitingRequests);
284 | }
285 | }
286 | }
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/requestvolley/src/main/java/com/jude/volley/toolbox/BasicNetwork.java:
--------------------------------------------------------------------------------
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 com.jude.volley.toolbox;
18 |
19 | import android.os.SystemClock;
20 |
21 | import com.jude.volley.AuthFailureError;
22 | import com.jude.volley.Cache;
23 | import com.jude.volley.Network;
24 | import com.jude.volley.NetworkError;
25 | import com.jude.volley.NetworkResponse;
26 | import com.jude.volley.NoConnectionError;
27 | import com.jude.volley.Request;
28 | import com.jude.volley.RetryPolicy;
29 | import com.jude.volley.ServerError;
30 | import com.jude.volley.TimeoutError;
31 | import com.jude.volley.VolleyError;
32 | import com.jude.volley.VolleyLog;
33 |
34 | import org.apache.http.Header;
35 | import org.apache.http.HttpEntity;
36 | import org.apache.http.HttpResponse;
37 | import org.apache.http.HttpStatus;
38 | import org.apache.http.StatusLine;
39 | import org.apache.http.conn.ConnectTimeoutException;
40 | import org.apache.http.impl.cookie.DateUtils;
41 |
42 | import java.io.IOException;
43 | import java.io.InputStream;
44 | import java.net.MalformedURLException;
45 | import java.net.SocketTimeoutException;
46 | import java.util.Date;
47 | import java.util.HashMap;
48 | import java.util.Map;
49 |
50 | /**
51 | * A network performing Volley requests over an {@link HttpStack}.
52 | */
53 | public class BasicNetwork implements Network {
54 | protected static final boolean DEBUG = VolleyLog.DEBUG;
55 |
56 | private static int SLOW_REQUEST_THRESHOLD_MS = 3000;
57 |
58 | private static int DEFAULT_POOL_SIZE = 4096;
59 |
60 | protected final HttpStack mHttpStack;
61 |
62 | protected final ByteArrayPool mPool;
63 |
64 | /**
65 | * @param httpStack HTTP stack to be used
66 | */
67 | public BasicNetwork(HttpStack httpStack) {
68 | // If a pool isn't passed in, then build a small default pool that will give us a lot of
69 | // benefit and not use too much memory.
70 | this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
71 | }
72 |
73 | /**
74 | * @param httpStack HTTP stack to be used
75 | * @param pool a buffer pool that improves GC performance in copy operations
76 | */
77 | public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
78 | mHttpStack = httpStack;
79 | mPool = pool;
80 | }
81 |
82 | @Override
83 | public NetworkResponse performRequest(Request> request) throws VolleyError {
84 | long requestStart = SystemClock.elapsedRealtime();
85 | while (true) {
86 | HttpResponse httpResponse = null;
87 | byte[] responseContents = null;
88 | Map responseHeaders = new HashMap();
89 | try {
90 | // Gather headers.
91 | Map headers = new HashMap();
92 | addCacheHeaders(headers, request.getCacheEntry());
93 | httpResponse = mHttpStack.performRequest(request, headers);
94 | StatusLine statusLine = httpResponse.getStatusLine();
95 | int statusCode = statusLine.getStatusCode();
96 |
97 | responseHeaders = convertHeaders(httpResponse.getAllHeaders());
98 | // Handle cache validation.
99 | if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
100 | return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
101 | request.getCacheEntry() == null ? null : request.getCacheEntry().data,
102 | responseHeaders, true);
103 | }
104 |
105 | // Handle moved resources
106 | if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
107 | String newUrl = responseHeaders.get("Location");
108 | request.setRedirectUrl(newUrl);
109 | }
110 |
111 | // Some responses such as 204s do not have content. We must check.
112 | if (httpResponse.getEntity() != null) {
113 | responseContents = entityToBytes(httpResponse.getEntity());
114 | } else {
115 | // Add 0 byte response as a way of honestly representing a
116 | // no-content request.
117 | responseContents = new byte[0];
118 | }
119 |
120 | // if the request is slow, log it.
121 | long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
122 | logSlowRequests(requestLifetime, request, responseContents, statusLine);
123 |
124 | if (statusCode < 200 || statusCode > 299) {
125 | throw new IOException();
126 | }
127 | return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
128 | } catch (SocketTimeoutException e) {
129 | attemptRetryOnException("socket", request, new TimeoutError());
130 | } catch (ConnectTimeoutException e) {
131 | attemptRetryOnException("connection", request, new TimeoutError());
132 | } catch (MalformedURLException e) {
133 | throw new RuntimeException("Bad URL " + request.getUrl(), e);
134 | } catch (IOException e) {
135 | int statusCode = 0;
136 | NetworkResponse networkResponse = null;
137 | if (httpResponse != null) {
138 | statusCode = httpResponse.getStatusLine().getStatusCode();
139 | } else {
140 | throw new NoConnectionError(e);
141 | }
142 | if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
143 | statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
144 | VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl());
145 | } else {
146 | VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
147 | }
148 | if (responseContents != null) {
149 | networkResponse = new NetworkResponse(statusCode, responseContents,
150 | responseHeaders, false);
151 | if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
152 | statusCode == HttpStatus.SC_FORBIDDEN) {
153 | attemptRetryOnException("auth",
154 | request, new AuthFailureError(networkResponse));
155 | } else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
156 | statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
157 | attemptRetryOnException("redirect",
158 | request, new AuthFailureError(networkResponse));
159 | } else {
160 | // TODO: Only throw ServerError for 5xx status codes.
161 | throw new ServerError(networkResponse);
162 | }
163 | } else {
164 | throw new NetworkError(networkResponse);
165 | }
166 | }
167 | }
168 | }
169 |
170 | /**
171 | * Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete.
172 | */
173 | private void logSlowRequests(long requestLifetime, Request> request,
174 | byte[] responseContents, StatusLine statusLine) {
175 | if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) {
176 | VolleyLog.d("HTTP response for request=<%s> [lifetime=%d], [size=%s], " +
177 | "[rc=%d], [retryCount=%s]", request, requestLifetime,
178 | responseContents != null ? responseContents.length : "null",
179 | statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount());
180 | }
181 | }
182 |
183 | /**
184 | * Attempts to prepare the request for a retry. If there are no more attempts remaining in the
185 | * request's retry policy, a timeout exception is thrown.
186 | * @param request The request to use.
187 | */
188 | private static void attemptRetryOnException(String logPrefix, Request> request,
189 | VolleyError exception) throws VolleyError {
190 | RetryPolicy retryPolicy = request.getRetryPolicy();
191 | int oldTimeout = request.getTimeoutMs();
192 |
193 | try {
194 | retryPolicy.retry(exception);
195 | } catch (VolleyError e) {
196 | request.addMarker(
197 | String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
198 | throw e;
199 | }
200 | request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
201 | }
202 |
203 | private void addCacheHeaders(Map headers, Cache.Entry entry) {
204 | // If there's no cache entry, we're done.
205 | if (entry == null) {
206 | return;
207 | }
208 |
209 | if (entry.etag != null) {
210 | headers.put("If-None-Match", entry.etag);
211 | }
212 |
213 | if (entry.serverDate > 0) {
214 | Date refTime = new Date(entry.serverDate);
215 | headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
216 | }
217 | }
218 |
219 | protected void logError(String what, String url, long start) {
220 | long now = SystemClock.elapsedRealtime();
221 | VolleyLog.v("HTTP ERROR(%s) %d ms to fetch %s", what, (now - start), url);
222 | }
223 |
224 | /** Reads the contents of HttpEntity into a byte[]. */
225 | private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError {
226 | PoolingByteArrayOutputStream bytes =
227 | new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength());
228 | byte[] buffer = null;
229 | try {
230 | InputStream in = entity.getContent();
231 | if (in == null) {
232 | throw new ServerError();
233 | }
234 | buffer = mPool.getBuf(1024);
235 | int count;
236 | while ((count = in.read(buffer)) != -1) {
237 | bytes.write(buffer, 0, count);
238 | }
239 | return bytes.toByteArray();
240 | } finally {
241 | try {
242 | // Close the InputStream and release the resources by "consuming the content".
243 | entity.consumeContent();
244 | } catch (IOException e) {
245 | // This can happen if there was an exception above that left the entity in
246 | // an invalid state.
247 | VolleyLog.v("Error occured when calling consumingContent");
248 | }
249 | mPool.returnBuf(buffer);
250 | bytes.close();
251 | }
252 | }
253 |
254 | /**
255 | * Converts Headers[] to Map.
256 | */
257 | private static Map convertHeaders(Header[] headers) {
258 | Map result = new HashMap();
259 | for (int i = 0; i < headers.length; i++) {
260 | result.put(headers[i].getName(), headers[i].getValue());
261 | }
262 | return result;
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/requestvolley/src/main/java/com/jude/volley/toolbox/DiskBasedCache.java:
--------------------------------------------------------------------------------
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 com.jude.volley.toolbox;
18 |
19 | import android.os.SystemClock;
20 |
21 | import com.jude.volley.Cache;
22 | import com.jude.volley.VolleyLog;
23 |
24 | import java.io.EOFException;
25 | import java.io.File;
26 | import java.io.FileInputStream;
27 | import java.io.FileOutputStream;
28 | import java.io.FilterInputStream;
29 | import java.io.IOException;
30 | import java.io.InputStream;
31 | import java.io.OutputStream;
32 | import java.util.Collections;
33 | import java.util.HashMap;
34 | import java.util.Iterator;
35 | import java.util.LinkedHashMap;
36 | import java.util.Map;
37 |
38 | /**
39 | * Cache implementation that caches files directly onto the hard disk in the specified
40 | * directory. The default disk usage size is 5MB, but is configurable.
41 | */
42 | public class DiskBasedCache implements Cache {
43 |
44 | /** Map of the Key, CacheHeader pairs */
45 | private final Map mEntries =
46 | new LinkedHashMap(16, .75f, true);
47 |
48 | /** Total amount of space currently used by the cache in bytes. */
49 | private long mTotalSize = 0;
50 |
51 | /** The root directory to use for the cache. */
52 | private final File mRootDirectory;
53 |
54 | /** The maximum size of the cache in bytes. */
55 | private final int mMaxCacheSizeInBytes;
56 |
57 | /** Default maximum disk usage in bytes. */
58 | private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
59 |
60 | /** High water mark percentage for the cache */
61 | private static final float HYSTERESIS_FACTOR = 0.9f;
62 |
63 | /** Magic number for current version of cache file format. */
64 | private static final int CACHE_MAGIC = 0x20120504;
65 |
66 | /**
67 | * Constructs an instance of the DiskBasedCache at the specified directory.
68 | * @param rootDirectory The root directory of the cache.
69 | * @param maxCacheSizeInBytes The maximum size of the cache in bytes.
70 | */
71 | public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
72 | mRootDirectory = rootDirectory;
73 | mMaxCacheSizeInBytes = maxCacheSizeInBytes;
74 | }
75 |
76 | /**
77 | * Constructs an instance of the DiskBasedCache at the specified directory using
78 | * the default maximum cache size of 5MB.
79 | * @param rootDirectory The root directory of the cache.
80 | */
81 | public DiskBasedCache(File rootDirectory) {
82 | this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
83 | }
84 |
85 | /**
86 | * Clears the cache. Deletes all cached files from disk.
87 | */
88 | @Override
89 | public synchronized void clear() {
90 | File[] files = mRootDirectory.listFiles();
91 | if (files != null) {
92 | for (File file : files) {
93 | file.delete();
94 | }
95 | }
96 | mEntries.clear();
97 | mTotalSize = 0;
98 | VolleyLog.d("Cache cleared.");
99 | }
100 |
101 | /**
102 | * Returns the cache entry with the specified key if it exists, null otherwise.
103 | */
104 | @Override
105 | public synchronized Entry get(String key) {
106 | CacheHeader entry = mEntries.get(key);
107 | // if the entry does not exist, return.
108 | if (entry == null) {
109 | return null;
110 | }
111 |
112 | File file = getFileForKey(key);
113 | CountingInputStream cis = null;
114 | try {
115 | cis = new CountingInputStream(new FileInputStream(file));
116 | CacheHeader.readHeader(cis); // eat header
117 | byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
118 | return entry.toCacheEntry(data);
119 | } catch (IOException e) {
120 | VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
121 | remove(key);
122 | return null;
123 | } finally {
124 | if (cis != null) {
125 | try {
126 | cis.close();
127 | } catch (IOException ioe) {
128 | return null;
129 | }
130 | }
131 | }
132 | }
133 |
134 | /**
135 | * Initializes the DiskBasedCache by scanning for all files currently in the
136 | * specified root directory. Creates the root directory if necessary.
137 | */
138 | @Override
139 | public synchronized void initialize() {
140 | if (!mRootDirectory.exists()) {
141 | if (!mRootDirectory.mkdirs()) {
142 | VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
143 | }
144 | return;
145 | }
146 |
147 | File[] files = mRootDirectory.listFiles();
148 | if (files == null) {
149 | return;
150 | }
151 | for (File file : files) {
152 | FileInputStream fis = null;
153 | try {
154 | fis = new FileInputStream(file);
155 | CacheHeader entry = CacheHeader.readHeader(fis);
156 | entry.size = file.length();
157 | putEntry(entry.key, entry);
158 | } catch (IOException e) {
159 | if (file != null) {
160 | file.delete();
161 | }
162 | } finally {
163 | try {
164 | if (fis != null) {
165 | fis.close();
166 | }
167 | } catch (IOException ignored) { }
168 | }
169 | }
170 | }
171 |
172 | /**
173 | * Invalidates an entry in the cache.
174 | * @param key Cache key
175 | * @param fullExpire True to fully expire the entry, false to soft expire
176 | */
177 | @Override
178 | public synchronized void invalidate(String key, boolean fullExpire) {
179 | Entry entry = get(key);
180 | if (entry != null) {
181 | entry.softTtl = 0;
182 | if (fullExpire) {
183 | entry.ttl = 0;
184 | }
185 | put(key, entry);
186 | }
187 |
188 | }
189 |
190 | /**
191 | * Puts the entry with the specified key into the cache.
192 | */
193 | @Override
194 | public synchronized void put(String key, Entry entry) {
195 | pruneIfNeeded(entry.data.length);
196 | File file = getFileForKey(key);
197 | try {
198 | FileOutputStream fos = new FileOutputStream(file);
199 | CacheHeader e = new CacheHeader(key, entry);
200 | e.writeHeader(fos);
201 | fos.write(entry.data);
202 | fos.close();
203 | putEntry(key, e);
204 | return;
205 | } catch (IOException e) {
206 | }
207 | boolean deleted = file.delete();
208 | if (!deleted) {
209 | VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
210 | }
211 | }
212 |
213 | /**
214 | * Removes the specified key from the cache if it exists.
215 | */
216 | @Override
217 | public synchronized void remove(String key) {
218 | boolean deleted = getFileForKey(key).delete();
219 | removeEntry(key);
220 | if (!deleted) {
221 | VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
222 | key, getFilenameForKey(key));
223 | }
224 | }
225 |
226 | /**
227 | * Creates a pseudo-unique filename for the specified cache key.
228 | * @param key The key to generate a file name for.
229 | * @return A pseudo-unique filename.
230 | */
231 | private String getFilenameForKey(String key) {
232 | int firstHalfLength = key.length() / 2;
233 | String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
234 | localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
235 | return localFilename;
236 | }
237 |
238 | /**
239 | * Returns a file object for the given cache key.
240 | */
241 | public File getFileForKey(String key) {
242 | return new File(mRootDirectory, getFilenameForKey(key));
243 | }
244 |
245 | /**
246 | * Prunes the cache to fit the amount of bytes specified.
247 | * @param neededSpace The amount of bytes we are trying to fit into the cache.
248 | */
249 | private void pruneIfNeeded(int neededSpace) {
250 | if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
251 | return;
252 | }
253 | if (VolleyLog.DEBUG) {
254 | VolleyLog.v("Pruning old cache entries.");
255 | }
256 |
257 | long before = mTotalSize;
258 | int prunedFiles = 0;
259 | long startTime = SystemClock.elapsedRealtime();
260 |
261 | Iterator> iterator = mEntries.entrySet().iterator();
262 | while (iterator.hasNext()) {
263 | Map.Entry entry = iterator.next();
264 | CacheHeader e = entry.getValue();
265 | boolean deleted = getFileForKey(e.key).delete();
266 | if (deleted) {
267 | mTotalSize -= e.size;
268 | } else {
269 | VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
270 | e.key, getFilenameForKey(e.key));
271 | }
272 | iterator.remove();
273 | prunedFiles++;
274 |
275 | if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
276 | break;
277 | }
278 | }
279 |
280 | if (VolleyLog.DEBUG) {
281 | VolleyLog.v("pruned %d files, %d bytes, %d ms",
282 | prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);
283 | }
284 | }
285 |
286 | /**
287 | * Puts the entry with the specified key into the cache.
288 | * @param key The key to identify the entry by.
289 | * @param entry The entry to cache.
290 | */
291 | private void putEntry(String key, CacheHeader entry) {
292 | if (!mEntries.containsKey(key)) {
293 | mTotalSize += entry.size;
294 | } else {
295 | CacheHeader oldEntry = mEntries.get(key);
296 | mTotalSize += (entry.size - oldEntry.size);
297 | }
298 | mEntries.put(key, entry);
299 | }
300 |
301 | /**
302 | * Removes the entry identified by 'key' from the cache.
303 | */
304 | private void removeEntry(String key) {
305 | CacheHeader entry = mEntries.get(key);
306 | if (entry != null) {
307 | mTotalSize -= entry.size;
308 | mEntries.remove(key);
309 | }
310 | }
311 |
312 | /**
313 | * Reads the contents of an InputStream into a byte[].
314 | * */
315 | private static byte[] streamToBytes(InputStream in, int length) throws IOException {
316 | byte[] bytes = new byte[length];
317 | int count;
318 | int pos = 0;
319 | while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) {
320 | pos += count;
321 | }
322 | if (pos != length) {
323 | throw new IOException("Expected " + length + " bytes, read " + pos + " bytes");
324 | }
325 | return bytes;
326 | }
327 |
328 | /**
329 | * Handles holding onto the cache headers for an entry.
330 | */
331 | // Visible for testing.
332 | static class CacheHeader {
333 | /** The size of the data identified by this CacheHeader. (This is not
334 | * serialized to disk. */
335 | public long size;
336 |
337 | /** The key that identifies the cache entry. */
338 | public String key;
339 |
340 | /** ETag for cache coherence. */
341 | public String etag;
342 |
343 | /** Date of this response as reported by the server. */
344 | public long serverDate;
345 |
346 | /** TTL for this record. */
347 | public long ttl;
348 |
349 | /** Soft TTL for this record. */
350 | public long softTtl;
351 |
352 | /** Headers from the response resulting in this cache entry. */
353 | public Map responseHeaders;
354 |
355 | private CacheHeader() { }
356 |
357 | /**
358 | * Instantiates a new CacheHeader object
359 | * @param key The key that identifies the cache entry
360 | * @param entry The cache entry.
361 | */
362 | public CacheHeader(String key, Entry entry) {
363 | this.key = key;
364 | this.size = entry.data.length;
365 | this.etag = entry.etag;
366 | this.serverDate = entry.serverDate;
367 | this.ttl = entry.ttl;
368 | this.softTtl = entry.softTtl;
369 | this.responseHeaders = entry.responseHeaders;
370 | }
371 |
372 | /**
373 | * Reads the header off of an InputStream and returns a CacheHeader object.
374 | * @param is The InputStream to read from.
375 | * @throws IOException
376 | */
377 | public static CacheHeader readHeader(InputStream is) throws IOException {
378 | CacheHeader entry = new CacheHeader();
379 | int magic = readInt(is);
380 | if (magic != CACHE_MAGIC) {
381 | // don't bother deleting, it'll get pruned eventually
382 | throw new IOException();
383 | }
384 | entry.key = readString(is);
385 | entry.etag = readString(is);
386 | if (entry.etag.equals("")) {
387 | entry.etag = null;
388 | }
389 | entry.serverDate = readLong(is);
390 | entry.ttl = readLong(is);
391 | entry.softTtl = readLong(is);
392 | entry.responseHeaders = readStringStringMap(is);
393 | return entry;
394 | }
395 |
396 | /**
397 | * Creates a cache entry for the specified data.
398 | */
399 | public Entry toCacheEntry(byte[] data) {
400 | Entry e = new Entry();
401 | e.data = data;
402 | e.etag = etag;
403 | e.serverDate = serverDate;
404 | e.ttl = ttl;
405 | e.softTtl = softTtl;
406 | e.responseHeaders = responseHeaders;
407 | return e;
408 | }
409 |
410 |
411 | /**
412 | * Writes the contents of this CacheHeader to the specified OutputStream.
413 | */
414 | public boolean writeHeader(OutputStream os) {
415 | try {
416 | writeInt(os, CACHE_MAGIC);
417 | writeString(os, key);
418 | writeString(os, etag == null ? "" : etag);
419 | writeLong(os, serverDate);
420 | writeLong(os, ttl);
421 | writeLong(os, softTtl);
422 | writeStringStringMap(responseHeaders, os);
423 | os.flush();
424 | return true;
425 | } catch (IOException e) {
426 | VolleyLog.d("%s", e.toString());
427 | return false;
428 | }
429 | }
430 |
431 | }
432 |
433 | private static class CountingInputStream extends FilterInputStream {
434 | private int bytesRead = 0;
435 |
436 | private CountingInputStream(InputStream in) {
437 | super(in);
438 | }
439 |
440 | @Override
441 | public int read() throws IOException {
442 | int result = super.read();
443 | if (result != -1) {
444 | bytesRead++;
445 | }
446 | return result;
447 | }
448 |
449 | @Override
450 | public int read(byte[] buffer, int offset, int count) throws IOException {
451 | int result = super.read(buffer, offset, count);
452 | if (result != -1) {
453 | bytesRead += result;
454 | }
455 | return result;
456 | }
457 | }
458 |
459 | /*
460 | * Homebrewed simple serialization system used for reading and writing cache
461 | * headers on disk. Once upon a time, this used the standard Java
462 | * Object{Input,Output}Stream, but the default implementation relies heavily
463 | * on reflection (even for standard types) and generates a ton of garbage.
464 | */
465 |
466 | /**
467 | * Simple wrapper around {@link InputStream#read()} that throws EOFException
468 | * instead of returning -1.
469 | */
470 | private static int read(InputStream is) throws IOException {
471 | int b = is.read();
472 | if (b == -1) {
473 | throw new EOFException();
474 | }
475 | return b;
476 | }
477 |
478 | static void writeInt(OutputStream os, int n) throws IOException {
479 | os.write((n >> 0) & 0xff);
480 | os.write((n >> 8) & 0xff);
481 | os.write((n >> 16) & 0xff);
482 | os.write((n >> 24) & 0xff);
483 | }
484 |
485 | static int readInt(InputStream is) throws IOException {
486 | int n = 0;
487 | n |= (read(is) << 0);
488 | n |= (read(is) << 8);
489 | n |= (read(is) << 16);
490 | n |= (read(is) << 24);
491 | return n;
492 | }
493 |
494 | static void writeLong(OutputStream os, long n) throws IOException {
495 | os.write((byte)(n >>> 0));
496 | os.write((byte)(n >>> 8));
497 | os.write((byte)(n >>> 16));
498 | os.write((byte)(n >>> 24));
499 | os.write((byte)(n >>> 32));
500 | os.write((byte)(n >>> 40));
501 | os.write((byte)(n >>> 48));
502 | os.write((byte)(n >>> 56));
503 | }
504 |
505 | static long readLong(InputStream is) throws IOException {
506 | long n = 0;
507 | n |= ((read(is) & 0xFFL) << 0);
508 | n |= ((read(is) & 0xFFL) << 8);
509 | n |= ((read(is) & 0xFFL) << 16);
510 | n |= ((read(is) & 0xFFL) << 24);
511 | n |= ((read(is) & 0xFFL) << 32);
512 | n |= ((read(is) & 0xFFL) << 40);
513 | n |= ((read(is) & 0xFFL) << 48);
514 | n |= ((read(is) & 0xFFL) << 56);
515 | return n;
516 | }
517 |
518 | static void writeString(OutputStream os, String s) throws IOException {
519 | byte[] b = s.getBytes("UTF-8");
520 | writeLong(os, b.length);
521 | os.write(b, 0, b.length);
522 | }
523 |
524 | static String readString(InputStream is) throws IOException {
525 | int n = (int) readLong(is);
526 | byte[] b = streamToBytes(is, n);
527 | return new String(b, "UTF-8");
528 | }
529 |
530 | static void writeStringStringMap(Map map, OutputStream os) throws IOException {
531 | if (map != null) {
532 | writeInt(os, map.size());
533 | for (Map.Entry entry : map.entrySet()) {
534 | writeString(os, entry.getKey());
535 | writeString(os, entry.getValue());
536 | }
537 | } else {
538 | writeInt(os, 0);
539 | }
540 | }
541 |
542 | static Map readStringStringMap(InputStream is) throws IOException {
543 | int size = readInt(is);
544 | Map result = (size == 0)
545 | ? Collections.emptyMap()
546 | : new HashMap(size);
547 | for (int i = 0; i < size; i++) {
548 | String key = readString(is).intern();
549 | String value = readString(is).intern();
550 | result.put(key, value);
551 | }
552 | return result;
553 | }
554 |
555 |
556 | }
557 |
--------------------------------------------------------------------------------