Skip to content

Commit 38ecd59

Browse files
committed
Implement request tagging and pause/resume support
1 parent c21d17f commit 38ecd59

17 files changed

+461
-47
lines changed

picasso/src/main/java/com/squareup/picasso/Action.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,13 @@ public RequestWeakReference(Action action, T referent, ReferenceQueue<? super T>
3939
final int errorResId;
4040
final Drawable errorDrawable;
4141
final String key;
42+
final Object tag;
4243

4344
boolean willReplay;
4445
boolean cancelled;
4546

4647
Action(Picasso picasso, T target, Request request, boolean skipCache, boolean noFade,
47-
int errorResId, Drawable errorDrawable, String key) {
48+
int errorResId, Drawable errorDrawable, String key, Object tag) {
4849
this.picasso = picasso;
4950
this.request = request;
5051
this.target = new RequestWeakReference<T>(this, target, picasso.referenceQueue);
@@ -53,6 +54,7 @@ public RequestWeakReference(Action action, T referent, ReferenceQueue<? super T>
5354
this.errorResId = errorResId;
5455
this.errorDrawable = errorDrawable;
5556
this.key = key;
57+
this.tag = (tag != null ? tag : this);
5658
}
5759

5860
abstract void complete(Bitmap result, Picasso.LoadedFrom from);
@@ -90,4 +92,8 @@ Picasso getPicasso() {
9092
Priority getPriority() {
9193
return request.priority;
9294
}
95+
96+
Object getTag() {
97+
return tag;
98+
}
9399
}

picasso/src/main/java/com/squareup/picasso/Dispatcher.java

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
import android.os.Looper;
2828
import android.os.Message;
2929
import java.util.ArrayList;
30+
import java.util.HashSet;
3031
import java.util.Iterator;
3132
import java.util.LinkedHashMap;
3233
import java.util.List;
3334
import java.util.Map;
35+
import java.util.Set;
3436
import java.util.WeakHashMap;
3537
import java.util.concurrent.ExecutorService;
3638

@@ -45,6 +47,7 @@
4547
import static com.squareup.picasso.Utils.VERB_DELIVERED;
4648
import static com.squareup.picasso.Utils.VERB_ENQUEUED;
4749
import static com.squareup.picasso.Utils.VERB_IGNORED;
50+
import static com.squareup.picasso.Utils.VERB_PAUSED;
4851
import static com.squareup.picasso.Utils.VERB_REPLAYING;
4952
import static com.squareup.picasso.Utils.VERB_RETRYING;
5053
import static com.squareup.picasso.Utils.getLogIdsForHunter;
@@ -67,6 +70,9 @@ class Dispatcher {
6770
static final int HUNTER_BATCH_COMPLETE = 8;
6871
static final int NETWORK_STATE_CHANGE = 9;
6972
static final int AIRPLANE_MODE_CHANGE = 10;
73+
static final int TAG_PAUSE = 11;
74+
static final int TAG_RESUME = 12;
75+
static final int REQUEST_BATCH_RESUME = 13;
7076

7177
private static final String DISPATCHER_THREAD_NAME = "Dispatcher";
7278
private static final int BATCH_DELAY = 200; // ms
@@ -77,6 +83,8 @@ class Dispatcher {
7783
final Downloader downloader;
7884
final Map<String, BitmapHunter> hunterMap;
7985
final Map<Object, Action> failedActions;
86+
final Map<Object, Action> pausedActions;
87+
final Set<Object> pausedTags;
8088
final Handler handler;
8189
final Handler mainThreadHandler;
8290
final Cache cache;
@@ -95,6 +103,8 @@ class Dispatcher {
95103
this.service = service;
96104
this.hunterMap = new LinkedHashMap<String, BitmapHunter>();
97105
this.failedActions = new WeakHashMap<Object, Action>();
106+
this.pausedActions = new WeakHashMap<Object, Action>();
107+
this.pausedTags = new HashSet<Object>();
98108
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
99109
this.downloader = downloader;
100110
this.mainThreadHandler = mainThreadHandler;
@@ -121,6 +131,14 @@ void dispatchCancel(Action action) {
121131
handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action));
122132
}
123133

134+
void dispatchPauseTag(Object tag) {
135+
handler.sendMessage(handler.obtainMessage(TAG_PAUSE, tag));
136+
}
137+
138+
void dispatchResumeTag(Object tag) {
139+
handler.sendMessage(handler.obtainMessage(TAG_RESUME, tag));
140+
}
141+
124142
void dispatchComplete(BitmapHunter hunter) {
125143
handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
126144
}
@@ -143,6 +161,15 @@ void dispatchAirplaneModeChange(boolean airplaneMode) {
143161
}
144162

145163
void performSubmit(Action action) {
164+
if (pausedTags.contains(action.getTag())) {
165+
pausedActions.put(action.getTarget(), action);
166+
if (action.getPicasso().loggingEnabled) {
167+
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
168+
"because tag '" + action.getTag() + "' is paused");
169+
}
170+
return;
171+
}
172+
146173
BitmapHunter hunter = hunterMap.get(action.getKey());
147174
if (hunter != null) {
148175
hunter.attach(action);
@@ -178,12 +205,101 @@ void performCancel(Action action) {
178205
}
179206
}
180207
}
208+
209+
if (pausedTags.contains(action.getTag())) {
210+
pausedActions.remove(action.getTarget());
211+
if (action.getPicasso().loggingEnabled) {
212+
log(OWNER_DISPATCHER, VERB_CANCELED, action.getRequest().logId(),
213+
"because paused request got canceled");
214+
}
215+
}
216+
181217
Action remove = failedActions.remove(action.getTarget());
182218
if (remove != null && remove.getPicasso().loggingEnabled) {
183219
log(OWNER_DISPATCHER, VERB_CANCELED, remove.getRequest().logId(), "from replaying");
184220
}
185221
}
186222

223+
void performPauseTag(Object tag) {
224+
// Trying to pause a tag that is already paused.
225+
if (!pausedTags.add(tag)) {
226+
return;
227+
}
228+
229+
// Go through all active hunters and detach/pause the requests
230+
// that have the paused tag.
231+
for (Iterator<BitmapHunter> it = hunterMap.values().iterator(); it.hasNext();) {
232+
BitmapHunter hunter = it.next();
233+
boolean loggingEnabled = hunter.getPicasso().loggingEnabled;
234+
235+
Action single = hunter.getAction();
236+
List<Action> joined = hunter.getActions();
237+
boolean hasMultiple = joined != null && !joined.isEmpty();
238+
239+
// Hunter has no requests, bail early.
240+
if (single == null && !hasMultiple) {
241+
continue;
242+
}
243+
244+
if (single != null && single.getTag().equals(tag)) {
245+
hunter.detach(single);
246+
pausedActions.put(single.getTarget(), single);
247+
if (loggingEnabled) {
248+
log(OWNER_DISPATCHER, VERB_PAUSED, single.request.logId(),
249+
"because tag '" + tag + "' was paused");
250+
}
251+
}
252+
253+
if (hasMultiple) {
254+
for (int i = joined.size() - 1; i >= 0; i--) {
255+
Action action = joined.get(i);
256+
if (!action.getTag().equals(tag)) {
257+
continue;
258+
}
259+
260+
hunter.detach(action);
261+
pausedActions.put(action.getTarget(), action);
262+
if (loggingEnabled) {
263+
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
264+
"because tag '" + tag + "' was paused");
265+
}
266+
}
267+
}
268+
269+
// Check if the hunter can be cancelled in case all its requests
270+
// had the tag being paused here.
271+
if (hunter.cancel()) {
272+
it.remove();
273+
if (loggingEnabled) {
274+
log(OWNER_DISPATCHER, VERB_CANCELED, getLogIdsForHunter(hunter), "all actions paused");
275+
}
276+
}
277+
}
278+
}
279+
280+
void performResumeTag(Object tag) {
281+
// Trying to resume a tag that is not paused.
282+
if (!pausedTags.remove(tag)) {
283+
return;
284+
}
285+
286+
List<Action> batch = null;
287+
for (Iterator<Action> i = pausedActions.values().iterator(); i.hasNext();) {
288+
Action action = i.next();
289+
if (action.getTag().equals(tag)) {
290+
if (batch == null) {
291+
batch = new ArrayList<Action>();
292+
}
293+
batch.add(action);
294+
i.remove();
295+
}
296+
}
297+
298+
if (batch != null) {
299+
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(REQUEST_BATCH_RESUME, batch));
300+
}
301+
}
302+
187303
void performRetry(BitmapHunter hunter) {
188304
if (hunter.isCancelled()) return;
189305

@@ -350,6 +466,16 @@ public DispatcherHandler(Looper looper, Dispatcher dispatcher) {
350466
dispatcher.performCancel(action);
351467
break;
352468
}
469+
case TAG_PAUSE: {
470+
Object tag = msg.obj;
471+
dispatcher.performPauseTag(tag);
472+
break;
473+
}
474+
case TAG_RESUME: {
475+
Object tag = msg.obj;
476+
dispatcher.performResumeTag(tag);
477+
break;
478+
}
353479
case HUNTER_COMPLETE: {
354480
BitmapHunter hunter = (BitmapHunter) msg.obj;
355481
dispatcher.performComplete(hunter);

picasso/src/main/java/com/squareup/picasso/FetchAction.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
import android.graphics.Bitmap;
1919

2020
class FetchAction extends Action<Void> {
21-
FetchAction(Picasso picasso, Request data, boolean skipCache, String key) {
22-
super(picasso, null, data, skipCache, false, 0, null, key);
21+
FetchAction(Picasso picasso, Request data, boolean skipCache, String key, Object tag) {
22+
super(picasso, null, data, skipCache, false, 0, null, key, tag);
2323
}
2424

2525
@Override void complete(Bitmap result, Picasso.LoadedFrom from) {

picasso/src/main/java/com/squareup/picasso/GetAction.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
import android.graphics.Bitmap;
1919

2020
class GetAction extends Action<Void> {
21-
GetAction(Picasso picasso, Request data, boolean skipCache, String key) {
22-
super(picasso, null, data, skipCache, false, 0, null, key);
21+
GetAction(Picasso picasso, Request data, boolean skipCache, String key, Object tag) {
22+
super(picasso, null, data, skipCache, false, 0, null, key, tag);
2323
}
2424

2525
@Override void complete(Bitmap result, Picasso.LoadedFrom from) {

picasso/src/main/java/com/squareup/picasso/ImageViewAction.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ class ImageViewAction extends Action<ImageView> {
2525
Callback callback;
2626

2727
ImageViewAction(Picasso picasso, ImageView imageView, Request data, boolean skipCache,
28-
boolean noFade, int errorResId, Drawable errorDrawable, String key, Callback callback) {
29-
super(picasso, imageView, data, skipCache, noFade, errorResId, errorDrawable, key);
28+
boolean noFade, int errorResId, Drawable errorDrawable, String key, Object tag,
29+
Callback callback) {
30+
super(picasso, imageView, data, skipCache, noFade, errorResId, errorDrawable, key, tag);
3031
this.callback = callback;
3132
}
3233

picasso/src/main/java/com/squareup/picasso/Picasso.java

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,14 @@
3737
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
3838
import static com.squareup.picasso.Action.RequestWeakReference;
3939
import static com.squareup.picasso.Dispatcher.HUNTER_BATCH_COMPLETE;
40+
import static com.squareup.picasso.Dispatcher.REQUEST_BATCH_RESUME;
4041
import static com.squareup.picasso.Dispatcher.REQUEST_GCED;
42+
import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;
4143
import static com.squareup.picasso.Utils.OWNER_MAIN;
4244
import static com.squareup.picasso.Utils.THREAD_PREFIX;
4345
import static com.squareup.picasso.Utils.VERB_COMPLETED;
4446
import static com.squareup.picasso.Utils.VERB_ERRORED;
47+
import static com.squareup.picasso.Utils.VERB_RESUMED;
4548
import static com.squareup.picasso.Utils.checkMain;
4649
import static com.squareup.picasso.Utils.log;
4750

@@ -117,6 +120,13 @@ public enum Priority {
117120
action.picasso.cancelExistingRequest(action.getTarget());
118121
break;
119122
}
123+
case REQUEST_BATCH_RESUME:
124+
@SuppressWarnings("unchecked") List<Action> batch = (List<Action>) msg.obj;
125+
for (int i = 0, n = batch.size(); i < n; i++) {
126+
Action action = batch.get(i);
127+
action.picasso.resumeAction(action);
128+
}
129+
break;
120130
default:
121131
throw new AssertionError("Unknown handler message received: " + msg.what);
122132
}
@@ -197,6 +207,45 @@ public void cancelRequest(RemoteViews remoteViews, int viewId) {
197207
cancelExistingRequest(new RemoteViewsAction.RemoteViewsTarget(remoteViews, viewId));
198208
}
199209

210+
/**
211+
* Cancel any existing requests with given tag. You can set a tag
212+
* on new requests with {@link RequestCreator#tag(Object)}.
213+
*
214+
* @see RequestCreator#tag(Object)
215+
*/
216+
public void cancelTag(Object tag) {
217+
checkMain();
218+
List<Action> actions = new ArrayList<Action>(targetToAction.values());
219+
for (int i = 0, n = actions.size(); i < n; i++) {
220+
Action action = actions.get(i);
221+
if (action.getTag().equals(tag)) {
222+
cancelExistingRequest(action.getTarget());
223+
}
224+
}
225+
}
226+
227+
/**
228+
* Pause existing requests with the given tag. Use {@link #resumeTag(Object)}
229+
* to resume requests with the given tag.
230+
*
231+
* @see #resumeTag(Object)
232+
* @see RequestCreator#tag(Object)
233+
*/
234+
public void pauseTag(Object tag) {
235+
dispatcher.dispatchPauseTag(tag);
236+
}
237+
238+
/**
239+
* Resume paused requests with the given tag. Use {@link #pauseTag(Object)}
240+
* to pause requests with the given tag.
241+
*
242+
* @see #pauseTag(Object)
243+
* @see RequestCreator#tag(Object)
244+
*/
245+
public void resumeTag(Object tag) {
246+
dispatcher.dispatchResumeTag(tag);
247+
}
248+
200249
/**
201250
* Start an image request using the specified URI.
202251
* <p>
@@ -365,7 +414,7 @@ void defer(ImageView view, DeferredRequestCreator request) {
365414

366415
void enqueueAndSubmit(Action action) {
367416
Object target = action.getTarget();
368-
if (target != null) {
417+
if (target != null && targetToAction.get(target) != action) {
369418
// This will also check we are on the main thread.
370419
cancelExistingRequest(target);
371420
targetToAction.put(target, action);
@@ -420,6 +469,27 @@ void complete(BitmapHunter hunter) {
420469
}
421470
}
422471

472+
void resumeAction(Action action) {
473+
Bitmap bitmap = null;
474+
if (!action.skipCache) {
475+
bitmap = quickMemoryCacheCheck(action.getKey());
476+
}
477+
478+
if (bitmap != null) {
479+
// Resumed action is cached, complete immediately.
480+
deliverAction(bitmap, MEMORY, action);
481+
if (loggingEnabled) {
482+
log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + MEMORY);
483+
}
484+
} else {
485+
// Re-submit the action to the executor.
486+
enqueueAndSubmit(action);
487+
if (loggingEnabled) {
488+
log(OWNER_MAIN, VERB_RESUMED, action.request.logId());
489+
}
490+
}
491+
}
492+
423493
private void deliverAction(Bitmap result, LoadedFrom from, Action action) {
424494
if (action.isCancelled()) {
425495
return;

0 commit comments

Comments
 (0)