Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement request group and pause/resume support #665

Merged
merged 2 commits into from
Sep 23, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import android.widget.ListView;
import android.widget.ToggleButton;

import com.squareup.picasso.Picasso;

import static android.view.View.GONE;
import static android.view.View.VISIBLE;

Expand Down Expand Up @@ -40,6 +42,12 @@ public void onItemClick(AdapterView<?> adapterView, View view, int position, lon
});
}

@Override
protected void onDestroy() {
super.onDestroy();
Picasso.with(this).cancelTag(this);
}

@Override public void onBackPressed() {
if (showHide.isChecked()) {
showHide.setChecked(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public class SampleContactsActivity extends PicassoSampleActivity

ListView lv = (ListView) findViewById(android.R.id.list);
lv.setAdapter(adapter);
lv.setOnScrollListener(new SampleScrollListener(this));

getSupportLoaderManager().initLoader(ContactsQuery.QUERY_ID, null, this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public SampleContactsAdapter(Context context) {
Picasso.with(context)
.load(contactUri)
.placeholder(R.drawable.contact_picture_placeholder)
.tag(context)
.into(holder.icon);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ public class SampleGridViewActivity extends PicassoSampleActivity {

GridView gv = (GridView) findViewById(R.id.grid_view);
gv.setAdapter(new SampleGridViewAdapter(this));
gv.setOnScrollListener(new SampleScrollListener(this));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public SampleGridViewAdapter(Context context) {
.placeholder(R.drawable.placeholder) //
.error(R.drawable.error) //
.fit() //
.tag(context) //
.into(view);

return view;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public static ListFragment newInstance() {
ListView listView = (ListView) LayoutInflater.from(activity)
.inflate(R.layout.sample_list_detail_list, container, false);
listView.setAdapter(adapter);
listView.setOnScrollListener(new SampleScrollListener(activity));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Expand Down Expand Up @@ -83,6 +84,7 @@ public static DetailFragment newInstance(String url) {
Picasso.with(activity)
.load(url)
.fit()
.tag(activity)
.into(imageView);

return view;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public SampleListDetailAdapter(Context context) {
.error(R.drawable.error)
.resizeDimen(R.dimen.list_detail_image_size, R.dimen.list_detail_image_size)
.centerInside()
.tag(context)
.into(holder.image);

return view;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.example.picasso;

import android.content.Context;
import android.widget.AbsListView;

import com.squareup.picasso.Picasso;

public class SampleScrollListener implements AbsListView.OnScrollListener {
private final Context context;

public SampleScrollListener(Context context) {
this.context = context;
}

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
final Picasso picasso = Picasso.with(context);
if (scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_TOUCH_SCROLL) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No settling state :( Damn you, AbsListView.

picasso.resumeTag(context);
} else {
picasso.pauseTag(context);
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
// Do nothing.
}
}
8 changes: 7 additions & 1 deletion picasso/src/main/java/com/squareup/picasso/Action.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ public RequestWeakReference(Action action, T referent, ReferenceQueue<? super T>
final int errorResId;
final Drawable errorDrawable;
final String key;
final Object tag;

boolean willReplay;
boolean cancelled;

Action(Picasso picasso, T target, Request request, boolean skipCache, boolean noFade,
int errorResId, Drawable errorDrawable, String key) {
int errorResId, Drawable errorDrawable, String key, Object tag) {
this.picasso = picasso;
this.request = request;
this.target = new RequestWeakReference<T>(this, target, picasso.referenceQueue);
Expand All @@ -53,6 +54,7 @@ public RequestWeakReference(Action action, T referent, ReferenceQueue<? super T>
this.errorResId = errorResId;
this.errorDrawable = errorDrawable;
this.key = key;
this.tag = (tag != null ? tag : this);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is using this actually useful? The instance is never exposed nor used as a tag.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two reasons for using 'this' here:

  • Ensure non-null invariant in the tag member i.e. no null checks needed and no potential NPEs in tag matching code.
  • Forces this tag to be private when undefined and ensures it won't be mixed with user-entered tags by mistake.

}

abstract void complete(Bitmap result, Picasso.LoadedFrom from);
Expand Down Expand Up @@ -90,4 +92,8 @@ Picasso getPicasso() {
Priority getPriority() {
return request.priority;
}

Object getTag() {
return tag;
}
}
126 changes: 126 additions & 0 deletions picasso/src/main/java/com/squareup/picasso/Dispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
import android.os.Looper;
import android.os.Message;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;

Expand All @@ -45,6 +47,7 @@
import static com.squareup.picasso.Utils.VERB_DELIVERED;
import static com.squareup.picasso.Utils.VERB_ENQUEUED;
import static com.squareup.picasso.Utils.VERB_IGNORED;
import static com.squareup.picasso.Utils.VERB_PAUSED;
import static com.squareup.picasso.Utils.VERB_REPLAYING;
import static com.squareup.picasso.Utils.VERB_RETRYING;
import static com.squareup.picasso.Utils.getLogIdsForHunter;
Expand All @@ -67,6 +70,9 @@ class Dispatcher {
static final int HUNTER_BATCH_COMPLETE = 8;
static final int NETWORK_STATE_CHANGE = 9;
static final int AIRPLANE_MODE_CHANGE = 10;
static final int TAG_PAUSE = 11;
static final int TAG_RESUME = 12;
static final int REQUEST_BATCH_RESUME = 13;

private static final String DISPATCHER_THREAD_NAME = "Dispatcher";
private static final int BATCH_DELAY = 200; // ms
Expand All @@ -77,6 +83,8 @@ class Dispatcher {
final Downloader downloader;
final Map<String, BitmapHunter> hunterMap;
final Map<Object, Action> failedActions;
final Map<Object, Action> pausedActions;
final Set<Object> pausedTags;
final Handler handler;
final Handler mainThreadHandler;
final Cache cache;
Expand All @@ -95,6 +103,8 @@ class Dispatcher {
this.service = service;
this.hunterMap = new LinkedHashMap<String, BitmapHunter>();
this.failedActions = new WeakHashMap<Object, Action>();
this.pausedActions = new WeakHashMap<Object, Action>();
this.pausedTags = new HashSet<Object>();
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
this.downloader = downloader;
this.mainThreadHandler = mainThreadHandler;
Expand All @@ -121,6 +131,14 @@ void dispatchCancel(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action));
}

void dispatchPauseTag(Object tag) {
handler.sendMessage(handler.obtainMessage(TAG_PAUSE, tag));
}

void dispatchResumeTag(Object tag) {
handler.sendMessage(handler.obtainMessage(TAG_RESUME, tag));
}

void dispatchComplete(BitmapHunter hunter) {
handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}
Expand All @@ -143,6 +161,15 @@ void dispatchAirplaneModeChange(boolean airplaneMode) {
}

void performSubmit(Action action) {
if (pausedTags.contains(action.getTag())) {
pausedActions.put(action.getTarget(), action);
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
"because tag '" + action.getTag() + "' is paused");
}
return;
}

BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null) {
hunter.attach(action);
Expand Down Expand Up @@ -178,12 +205,101 @@ void performCancel(Action action) {
}
}
}

if (pausedTags.contains(action.getTag())) {
pausedActions.remove(action.getTarget());
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_CANCELED, action.getRequest().logId(),
"because paused request got canceled");
}
}

Action remove = failedActions.remove(action.getTarget());
if (remove != null && remove.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_CANCELED, remove.getRequest().logId(), "from replaying");
}
}

void performPauseTag(Object tag) {
// Trying to pause a tag that is already paused.
if (!pausedTags.add(tag)) {
return;
}

// Go through all active hunters and detach/pause the requests
// that have the paused tag.
for (Iterator<BitmapHunter> it = hunterMap.values().iterator(); it.hasNext();) {
BitmapHunter hunter = it.next();
boolean loggingEnabled = hunter.getPicasso().loggingEnabled;

Action single = hunter.getAction();
List<Action> joined = hunter.getActions();
boolean hasMultiple = joined != null && !joined.isEmpty();

// Hunter has no requests, bail early.
if (single == null && !hasMultiple) {
continue;
}

if (single != null && single.getTag().equals(tag)) {
hunter.detach(single);
pausedActions.put(single.getTarget(), single);
if (loggingEnabled) {
log(OWNER_DISPATCHER, VERB_PAUSED, single.request.logId(),
"because tag '" + tag + "' was paused");
}
}

if (hasMultiple) {
for (int i = joined.size() - 1; i >= 0; i--) {
Action action = joined.get(i);
if (!action.getTag().equals(tag)) {
continue;
}

hunter.detach(action);
pausedActions.put(action.getTarget(), action);
if (loggingEnabled) {
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
"because tag '" + tag + "' was paused");
}
}
}

// Check if the hunter can be cancelled in case all its requests
// had the tag being paused here.
if (hunter.cancel()) {
it.remove();
if (loggingEnabled) {
log(OWNER_DISPATCHER, VERB_CANCELED, getLogIdsForHunter(hunter), "all actions paused");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

}
}
}
}

void performResumeTag(Object tag) {
// Trying to resume a tag that is not paused.
if (!pausedTags.remove(tag)) {
return;
}

List<Action> batch = null;
for (Iterator<Action> i = pausedActions.values().iterator(); i.hasNext();) {
Action action = i.next();
if (action.getTag().equals(tag)) {
if (batch == null) {
batch = new ArrayList<Action>();
}
batch.add(action);
i.remove();
}
}

if (batch != null) {
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(REQUEST_BATCH_RESUME, batch));
}
}

void performRetry(BitmapHunter hunter) {
if (hunter.isCancelled()) return;

Expand Down Expand Up @@ -350,6 +466,16 @@ public DispatcherHandler(Looper looper, Dispatcher dispatcher) {
dispatcher.performCancel(action);
break;
}
case TAG_PAUSE: {
Object tag = msg.obj;
dispatcher.performPauseTag(tag);
break;
}
case TAG_RESUME: {
Object tag = msg.obj;
dispatcher.performResumeTag(tag);
break;
}
case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
Expand Down
4 changes: 2 additions & 2 deletions picasso/src/main/java/com/squareup/picasso/FetchAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import android.graphics.Bitmap;

class FetchAction extends Action<Void> {
FetchAction(Picasso picasso, Request data, boolean skipCache, String key) {
super(picasso, null, data, skipCache, false, 0, null, key);
FetchAction(Picasso picasso, Request data, boolean skipCache, String key, Object tag) {
super(picasso, null, data, skipCache, false, 0, null, key, tag);
}

@Override void complete(Bitmap result, Picasso.LoadedFrom from) {
Expand Down
4 changes: 2 additions & 2 deletions picasso/src/main/java/com/squareup/picasso/GetAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import android.graphics.Bitmap;

class GetAction extends Action<Void> {
GetAction(Picasso picasso, Request data, boolean skipCache, String key) {
super(picasso, null, data, skipCache, false, 0, null, key);
GetAction(Picasso picasso, Request data, boolean skipCache, String key, Object tag) {
super(picasso, null, data, skipCache, false, 0, null, key, tag);
}

@Override void complete(Bitmap result, Picasso.LoadedFrom from) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ class ImageViewAction extends Action<ImageView> {
Callback callback;

ImageViewAction(Picasso picasso, ImageView imageView, Request data, boolean skipCache,
boolean noFade, int errorResId, Drawable errorDrawable, String key, Callback callback) {
super(picasso, imageView, data, skipCache, noFade, errorResId, errorDrawable, key);
boolean noFade, int errorResId, Drawable errorDrawable, String key, Object tag,
Callback callback) {
super(picasso, imageView, data, skipCache, noFade, errorResId, errorDrawable, key, tag);
this.callback = callback;
}

Expand Down
Loading