From a227cb0a9ffc59f53ce27d8c09150c26de2ea00b Mon Sep 17 00:00:00 2001 From: Hannes Dorfmann Date: Tue, 17 Jun 2014 17:58:33 +0200 Subject: [PATCH 01/10] DispatchingQueue --- .../picasso/SampleListDetailActivity.java | 4 + .../java/com/squareup/picasso/Dispatcher.java | 17 +- .../squareup/picasso/DispatchingQueue.java | 182 ++++++++++++++++++ .../java/com/squareup/picasso/Picasso.java | 22 +++ .../scrolling/PicassoScrollListener.java | 64 ++++++ 5 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 picasso/src/main/java/com/squareup/picasso/DispatchingQueue.java create mode 100644 picasso/src/main/java/com/squareup/picasso/scrolling/PicassoScrollListener.java diff --git a/picasso-sample/src/main/java/com/example/picasso/SampleListDetailActivity.java b/picasso-sample/src/main/java/com/example/picasso/SampleListDetailActivity.java index 890ec88bca..bb64e97cb3 100644 --- a/picasso-sample/src/main/java/com/example/picasso/SampleListDetailActivity.java +++ b/picasso-sample/src/main/java/com/example/picasso/SampleListDetailActivity.java @@ -11,6 +11,7 @@ import android.widget.ListView; import android.widget.TextView; import com.squareup.picasso.Picasso; +import com.squareup.picasso.scrolling.PicassoScrollListener; public class SampleListDetailActivity extends PicassoSampleActivity { @Override protected void onCreate(Bundle savedInstanceState) { @@ -50,6 +51,9 @@ public void onItemClick(AdapterView adapterView, View view, int position, lon activity.showDetails(url); } }); + + listView.setOnScrollListener(new PicassoScrollListener(activity)); + return listView; } } diff --git a/picasso/src/main/java/com/squareup/picasso/Dispatcher.java b/picasso/src/main/java/com/squareup/picasso/Dispatcher.java index 5ba6cfbdc6..93f82a5987 100644 --- a/picasso/src/main/java/com/squareup/picasso/Dispatcher.java +++ b/picasso/src/main/java/com/squareup/picasso/Dispatcher.java @@ -26,6 +26,7 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Message; + import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; @@ -84,6 +85,7 @@ class Dispatcher { final List batch; final NetworkBroadcastReceiver receiver; final boolean scansNetworkChanges; + final DispatchingQueue dispatchingQueue; boolean airplaneMode; @@ -96,6 +98,7 @@ class Dispatcher { this.hunterMap = new LinkedHashMap(); this.failedActions = new WeakHashMap(); this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this); + this.dispatchingQueue = new DispatchingQueue(handler); this.downloader = downloader; this.mainThreadHandler = mainThreadHandler; this.cache = cache; @@ -122,7 +125,7 @@ void dispatchCancel(Action action) { } void dispatchComplete(BitmapHunter hunter) { - handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter)); + dispatchingQueue.dispatchComplete(hunter); } void dispatchRetry(BitmapHunter hunter) { @@ -130,7 +133,7 @@ void dispatchRetry(BitmapHunter hunter) { } void dispatchFailed(BitmapHunter hunter) { - handler.sendMessage(handler.obtainMessage(HUNTER_DECODE_FAILED, hunter)); + dispatchingQueue.dispatchComplete(hunter); } void dispatchNetworkStateChange(NetworkInfo info) { @@ -173,6 +176,7 @@ void performCancel(Action action) { hunter.detach(action); if (hunter.cancel()) { hunterMap.remove(key); + dispatchingQueue.dequeue(hunter); if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_CANCELED, action.getRequest().logId()); } @@ -316,6 +320,15 @@ private void batch(BitmapHunter hunter) { } } + public void interruptDispatching() { + dispatchingQueue.interruptDispatching(); + } + + + public void continueDispatching() { + dispatchingQueue.continueDispatching(); + } + private void logBatch(List copy) { if (copy == null || copy.isEmpty()) return; BitmapHunter hunter = copy.get(0); diff --git a/picasso/src/main/java/com/squareup/picasso/DispatchingQueue.java b/picasso/src/main/java/com/squareup/picasso/DispatchingQueue.java new file mode 100644 index 0000000000..d08c1be96e --- /dev/null +++ b/picasso/src/main/java/com/squareup/picasso/DispatchingQueue.java @@ -0,0 +1,182 @@ +package com.squareup.picasso; + +import android.os.Handler; + +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * A queue that manages dispatching the BitmapHunter result + * @author Hannes Dorfmann + */ +public class DispatchingQueue { + + + final Map hunterMap; + final Queue jobQueue; + final Handler handler; + int dispInterruptedCount; + + + public DispatchingQueue(Handler handler) { + this.handler = handler; + this.jobQueue = new ConcurrentLinkedQueue(); + this.hunterMap = new ConcurrentHashMap(); + this.dispInterruptedCount = 0; + } + + + public void interruptDispatching(){ + Utils.checkMain(); + dispInterruptedCount++; + } + + public void continueDispatching(){ + Utils.checkMain(); + + dispInterruptedCount--; + + if (dispInterruptedCount < 0){ + // Should never be reached + dispInterruptedCount = 0; + } + + if (isDispatchingEnabled()){ + dispatchNextFromQueue(); + } + } + + + public boolean isDispatchingEnabled(){ + return dispInterruptedCount == 0; + } + + + private void scheduleJob(DispatchJob job){ + boolean added = jobQueue.offer(job); + + if (added){ + hunterMap.put(job.getBitmapHunter(), job); + } + } + + + + public void dispatchComplete(BitmapHunter hunter){ + + if (isDispatchingEnabled()){ + // dispatch directly; avoid object creation overhead + handler.sendMessage(handler.obtainMessage(Dispatcher.HUNTER_COMPLETE, hunter)); + } else { + // dispatching is disabled temporarly + CompleteDispatchJob job = new CompleteDispatchJob(handler, hunter); + jobQueue.offer(job); + } + + } + + + public void dispatchFailed(BitmapHunter hunter){ + + if (isDispatchingEnabled()){ + // dispatch directly; avoir object creation overhead + handler.sendMessage(handler.obtainMessage(Dispatcher.HUNTER_DECODE_FAILED, hunter)); + } else { + // dispatching is disabled temporarly + FailedDispatchJob job = new FailedDispatchJob(handler, hunter); + jobQueue.offer(job); + } + + } + + + public void dispatchNextFromQueue(){ + + // TODO limit the count of simultaneous dispatches?!?!? + while (!jobQueue.isEmpty()) { + DispatchJob job = jobQueue.poll(); + + if (job != null) { + hunterMap.remove(job.getBitmapHunter()); + job.dispatch(); + } + + } + + } + + public void dequeue(BitmapHunter hunter) { + + DispatchJob job = hunterMap.get(hunter); + if (job != null){ + // Remove the waiting jo from queue + jobQueue.remove(job); + hunterMap.remove(hunter); + } + } + + + /** + * Base class for dispatch jobs + */ + static abstract class DispatchJob{ + + protected final Handler handler; + protected final BitmapHunter hunter; + + protected DispatchJob(Handler handler, BitmapHunter hunter) { + this.handler = handler; + this.hunter = hunter; + } + + public BitmapHunter getBitmapHunter(){ + return hunter; + } + + /** + * Dispatches the message + */ + public abstract void dispatch(); + } + + + /** + * A job that will dispatch that the BitmapHunter has completetd his job + */ + static class CompleteDispatchJob extends DispatchJob { + + + + protected CompleteDispatchJob(Handler handler, BitmapHunter hunter) { + super(handler, hunter); + } + + public void dispatch(){ + handler.sendMessage(handler.obtainMessage(Dispatcher.HUNTER_COMPLETE, hunter)); + } + + } + + + /** + * A job to dispatch that the BitmapHunter has failed + */ + static class FailedDispatchJob extends DispatchJob { + + + FailedDispatchJob(Handler handler, BitmapHunter hunter) { + super(handler, hunter); + } + + + public void dispatch(){ + handler.sendMessage(handler.obtainMessage(Dispatcher.HUNTER_DECODE_FAILED, hunter)); + } + } + + + + +} diff --git a/picasso/src/main/java/com/squareup/picasso/Picasso.java b/picasso/src/main/java/com/squareup/picasso/Picasso.java index dd08e99f53..2e0bd8bcea 100644 --- a/picasso/src/main/java/com/squareup/picasso/Picasso.java +++ b/picasso/src/main/java/com/squareup/picasso/Picasso.java @@ -24,6 +24,7 @@ import android.os.Message; import android.os.Process; import android.widget.ImageView; + import java.io.File; import java.lang.ref.ReferenceQueue; import java.util.List; @@ -413,8 +414,29 @@ private void cancelExistingRequest(Object target) { deferredRequestCreator.cancel(); } } + + // TODO right place to cancel dispatcher queue + } + public void interruptDispatching() { + if (dispatcher == null){ + throw new NullPointerException("The dispatcher is null"); + } + + dispatcher.interruptDispatching(); + } + + + public void continueDispatching(){ + if (dispatcher == null) { + throw new NullPointerException("The dispatcher is null"); + } + + dispatcher.continueDispatching(); + } + + private static class CleanupThread extends Thread { private final ReferenceQueue referenceQueue; private final Handler handler; diff --git a/picasso/src/main/java/com/squareup/picasso/scrolling/PicassoScrollListener.java b/picasso/src/main/java/com/squareup/picasso/scrolling/PicassoScrollListener.java new file mode 100644 index 0000000000..8b14306152 --- /dev/null +++ b/picasso/src/main/java/com/squareup/picasso/scrolling/PicassoScrollListener.java @@ -0,0 +1,64 @@ +package com.squareup.picasso.scrolling; + +import android.content.Context; +import android.widget.AbsListView; + +import com.squareup.picasso.Picasso; + +/** + * A simple scroll listener that disables fading / setting image drawable while the AbsListView is flinging + * @author Hannes Dorfmann + */ +public class PicassoScrollListener implements AbsListView.OnScrollListener { + + + protected AbsListView.OnScrollListener delegate; + protected Picasso picasso; + + public PicassoScrollListener(Picasso picasso, AbsListView.OnScrollListener delegate){ + this.delegate = delegate; + this.picasso = picasso; + } + + public PicassoScrollListener(Picasso picasso){ + this(picasso, null); + } + + public PicassoScrollListener(Context context){ + this(context, null); + } + + public PicassoScrollListener(Context context, AbsListView.OnScrollListener delegate){ + this(Picasso.with(context), delegate); + } + + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + + // TO the picasso staff + if (SCROLL_STATE_IDLE == scrollState ){ + picasso.continueDispatching(); + } + + if (SCROLL_STATE_FLING == scrollState){ + picasso.interruptDispatching(); + } + + // Forwart to the delegate + if (delegate != null){ + delegate.onScrollStateChanged(view, scrollState); + } + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + + + // Forward to the delegate + if (delegate != null){ + delegate.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); + } + + } +} From c6822c7ea0b096a0030091bf407e67774abcdb25 Mon Sep 17 00:00:00 2001 From: Hannes Dorfmann Date: Wed, 18 Jun 2014 02:00:57 +0200 Subject: [PATCH 02/10] PicasooFlingScrollListner; First UnitTests --- .../picasso/SampleGridViewActivity.java | 3 + .../java/com/squareup/picasso/Dispatcher.java | 1 + .../squareup/picasso/DispatchingQueue.java | 9 +- .../scrolling/PicassoFlingScrollListener.java | 64 ++++++++ .../scrolling/PicassoScrollListener.java | 14 +- .../com/squareup/picasso/DispatcherTest.java | 10 +- .../picasso/DispatchingQueueTest.java | 155 ++++++++++++++++++ 7 files changed, 248 insertions(+), 8 deletions(-) create mode 100644 picasso/src/main/java/com/squareup/picasso/scrolling/PicassoFlingScrollListener.java create mode 100644 picasso/src/test/java/com/squareup/picasso/DispatchingQueueTest.java diff --git a/picasso-sample/src/main/java/com/example/picasso/SampleGridViewActivity.java b/picasso-sample/src/main/java/com/example/picasso/SampleGridViewActivity.java index 909797402d..ab8efdba3c 100644 --- a/picasso-sample/src/main/java/com/example/picasso/SampleGridViewActivity.java +++ b/picasso-sample/src/main/java/com/example/picasso/SampleGridViewActivity.java @@ -3,6 +3,8 @@ import android.os.Bundle; import android.widget.GridView; +import com.squareup.picasso.scrolling.PicassoScrollListener; + public class SampleGridViewActivity extends PicassoSampleActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -10,5 +12,6 @@ public class SampleGridViewActivity extends PicassoSampleActivity { GridView gv = (GridView) findViewById(R.id.grid_view); gv.setAdapter(new SampleGridViewAdapter(this)); + gv.setOnScrollListener(new PicassoScrollListener(this)); } } diff --git a/picasso/src/main/java/com/squareup/picasso/Dispatcher.java b/picasso/src/main/java/com/squareup/picasso/Dispatcher.java index 93f82a5987..d40fd7c39f 100644 --- a/picasso/src/main/java/com/squareup/picasso/Dispatcher.java +++ b/picasso/src/main/java/com/squareup/picasso/Dispatcher.java @@ -114,6 +114,7 @@ void shutdown() { service.shutdown(); dispatcherThread.quit(); receiver.unregister(); + dispatchingQueue.clear(); } void dispatchSubmit(Action action) { diff --git a/picasso/src/main/java/com/squareup/picasso/DispatchingQueue.java b/picasso/src/main/java/com/squareup/picasso/DispatchingQueue.java index d08c1be96e..4e9a6aed74 100644 --- a/picasso/src/main/java/com/squareup/picasso/DispatchingQueue.java +++ b/picasso/src/main/java/com/squareup/picasso/DispatchingQueue.java @@ -72,7 +72,7 @@ public void dispatchComplete(BitmapHunter hunter){ } else { // dispatching is disabled temporarly CompleteDispatchJob job = new CompleteDispatchJob(handler, hunter); - jobQueue.offer(job); + scheduleJob(job); } } @@ -86,7 +86,7 @@ public void dispatchFailed(BitmapHunter hunter){ } else { // dispatching is disabled temporarly FailedDispatchJob job = new FailedDispatchJob(handler, hunter); - jobQueue.offer(job); + scheduleJob(job); } } @@ -117,6 +117,11 @@ public void dequeue(BitmapHunter hunter) { } } + public void clear() { + jobQueue.clear(); + hunterMap.clear(); + } + /** * Base class for dispatch jobs diff --git a/picasso/src/main/java/com/squareup/picasso/scrolling/PicassoFlingScrollListener.java b/picasso/src/main/java/com/squareup/picasso/scrolling/PicassoFlingScrollListener.java new file mode 100644 index 0000000000..3cfb92c70a --- /dev/null +++ b/picasso/src/main/java/com/squareup/picasso/scrolling/PicassoFlingScrollListener.java @@ -0,0 +1,64 @@ +package com.squareup.picasso.scrolling; + +import android.content.Context; +import android.widget.AbsListView; + +import com.squareup.picasso.Picasso; + +/** + * A simple scroll listener that disables fading / setting image drawable while the AbsListView is flinging + * @author Hannes Dorfmann + */ +public class PicassoFlingScrollListener implements AbsListView.OnScrollListener { + + + protected AbsListView.OnScrollListener delegate; + protected Picasso picasso; + + public PicassoFlingScrollListener(Picasso picasso, AbsListView.OnScrollListener delegate){ + this.delegate = delegate; + this.picasso = picasso; + } + + public PicassoFlingScrollListener(Picasso picasso){ + this(picasso, null); + } + + public PicassoFlingScrollListener(Context context){ + this(context, null); + } + + public PicassoFlingScrollListener(Context context, AbsListView.OnScrollListener delegate){ + this(Picasso.with(context), delegate); + } + + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + + // TO the picasso staff + if (SCROLL_STATE_IDLE == scrollState ){ + picasso.continueDispatching(); + } + + if (SCROLL_STATE_FLING == scrollState){ + picasso.interruptDispatching(); + } + + // Forwart to the delegate + if (delegate != null){ + delegate.onScrollStateChanged(view, scrollState); + } + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + + + // Forward to the delegate + if (delegate != null){ + delegate.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); + } + + } +} diff --git a/picasso/src/main/java/com/squareup/picasso/scrolling/PicassoScrollListener.java b/picasso/src/main/java/com/squareup/picasso/scrolling/PicassoScrollListener.java index 8b14306152..b2af18f8d6 100644 --- a/picasso/src/main/java/com/squareup/picasso/scrolling/PicassoScrollListener.java +++ b/picasso/src/main/java/com/squareup/picasso/scrolling/PicassoScrollListener.java @@ -1,6 +1,7 @@ package com.squareup.picasso.scrolling; import android.content.Context; +import android.util.Log; import android.widget.AbsListView; import com.squareup.picasso.Picasso; @@ -14,6 +15,7 @@ public class PicassoScrollListener implements AbsListView.OnScrollListener { protected AbsListView.OnScrollListener delegate; protected Picasso picasso; + private int previousScrollState = SCROLL_STATE_IDLE; public PicassoScrollListener(Picasso picasso, AbsListView.OnScrollListener delegate){ this.delegate = delegate; @@ -37,20 +39,28 @@ public PicassoScrollListener(Context context, AbsListView.OnScrollListener deleg public void onScrollStateChanged(AbsListView view, int scrollState) { // TO the picasso staff - if (SCROLL_STATE_IDLE == scrollState ){ + if (!isScrolling(scrollState) && isScrolling(previousScrollState)){ picasso.continueDispatching(); + Log.d("Test", " continue"); } - if (SCROLL_STATE_FLING == scrollState){ + if ( isScrolling(scrollState) && !isScrolling(previousScrollState)){ picasso.interruptDispatching(); + Log.d("Test", " interrupt"); } + previousScrollState = scrollState; + // Forwart to the delegate if (delegate != null){ delegate.onScrollStateChanged(view, scrollState); } } + protected boolean isScrolling(int scrollState){ + return scrollState == SCROLL_STATE_FLING || scrollState == SCROLL_STATE_TOUCH_SCROLL; + } + @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { diff --git a/picasso/src/test/java/com/squareup/picasso/DispatcherTest.java b/picasso/src/test/java/com/squareup/picasso/DispatcherTest.java index 52c86cfec3..d2264b08ad 100644 --- a/picasso/src/test/java/com/squareup/picasso/DispatcherTest.java +++ b/picasso/src/test/java/com/squareup/picasso/DispatcherTest.java @@ -20,7 +20,7 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Handler; -import java.util.concurrent.ExecutorService; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,6 +28,8 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.util.concurrent.ExecutorService; + import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Intent.ACTION_AIRPLANE_MODE_CHANGED; import static android.content.pm.PackageManager.PERMISSION_DENIED; @@ -131,9 +133,9 @@ public class DispatcherTest { @Test public void performCancelDetachesRequestAndCleansUp() throws Exception { Target target = mockTarget(); - Action action = mockAction(URI_KEY_1, URI_1, target); - BitmapHunter hunter = mockHunter(URI_KEY_1, BITMAP_1, false); - hunter.attach(action); + Action action = mockAction(URI_KEY_1, URI_1, target); + BitmapHunter hunter = mockHunter(URI_KEY_1, BITMAP_1, false); + hunter.attach(action); when(hunter.cancel()).thenReturn(true); dispatcher.hunterMap.put(URI_KEY_1, hunter); dispatcher.failedActions.put(target, action); diff --git a/picasso/src/test/java/com/squareup/picasso/DispatchingQueueTest.java b/picasso/src/test/java/com/squareup/picasso/DispatchingQueueTest.java new file mode 100644 index 0000000000..c590abfe2e --- /dev/null +++ b/picasso/src/test/java/com/squareup/picasso/DispatchingQueueTest.java @@ -0,0 +1,155 @@ +package com.squareup.picasso; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.os.Handler; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.Random; +import java.util.concurrent.ExecutorService; + +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static com.squareup.picasso.TestUtils.BITMAP_1; +import static com.squareup.picasso.TestUtils.URI_KEY_1; +import static com.squareup.picasso.TestUtils.mockHunter; +import static org.fest.assertions.api.Assertions.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +/** + * @author Hannes Dorfmann + */ +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class DispatchingQueueTest { + + @Mock + Context context; + @Mock + ConnectivityManager connectivityManager; + @Mock + ExecutorService service; + @Mock + Handler mainThreadHandler; + @Mock Downloader downloader; + @Mock Cache cache; + @Mock Stats stats; + private Dispatcher dispatcher; + private DispatchingQueue dispatchingQueue; + + + @Before + public void setUp() throws Exception { + initMocks(this); + dispatcher = createDispatcher(); + dispatchingQueue = dispatcher.dispatchingQueue; + } + + @Test public void shutdownAndClearing() throws Exception { + dispatcher.interruptDispatching(); + + for (int i = 1 ; i<= 10; i++){ + BitmapHunter hunter = mockHunter(URI_KEY_1, BITMAP_1, false); + if (i % 2 == 0) { + dispatcher.dispatchComplete(hunter); + } else { + dispatcher.dispatchFailed(hunter); + } + assertThat(dispatchingQueue.hunterMap).hasSize(i); + assertThat(dispatchingQueue.jobQueue).hasSize(i); + } + + + dispatcher.shutdown(); + + assertThat(dispatchingQueue.hunterMap).isEmpty(); + assertThat(dispatchingQueue.jobQueue).isEmpty(); + + } + + + @Test + public void interruptingContinuing(){ + + // At the beginning dispatching should be enabled + assertThat(dispatchingQueue.isDispatchingEnabled()).isTrue(); + + dispatcher.interruptDispatching(); + assertThat(dispatchingQueue.isDispatchingEnabled()).isFalse(); + + dispatcher.interruptDispatching(); + assertThat(dispatchingQueue.isDispatchingEnabled()).isFalse(); + + + dispatcher.continueDispatching(); + assertThat(dispatchingQueue.isDispatchingEnabled()).isFalse(); + + dispatcher.continueDispatching(); + assertThat(dispatchingQueue.isDispatchingEnabled()).isTrue(); + + // Do random testing + Random r = new Random(); + int tests = r.nextInt(20)+10; + int interruptions = 0; + + + for (int i = 0; i< tests ; i++){ + + if (r.nextBoolean()){ + dispatcher.interruptDispatching(); + interruptions++; + } else { + dispatcher.continueDispatching(); + interruptions--; + if (interruptions < 0) { + interruptions = 0; + } + } + + if (interruptions > 0){ + assertThat(dispatchingQueue.isDispatchingEnabled()).isFalse(); + } else { + assertThat(dispatchingQueue.isDispatchingEnabled()).isTrue(); + } + } + + for (;interruptions > 1; interruptions --){ + dispatcher.continueDispatching(); + assertThat(dispatchingQueue.isDispatchingEnabled()).isFalse(); + } + + dispatcher.continueDispatching(); + assertThat(dispatchingQueue.isDispatchingEnabled()).isTrue(); + + + } + + + + private Dispatcher createDispatcher() { + return createDispatcher(service); + } + + private Dispatcher createDispatcher(boolean scansNetworkChanges) { + return createDispatcher(service, scansNetworkChanges); + } + + private Dispatcher createDispatcher(ExecutorService service) { + return createDispatcher(service, true); + } + + private Dispatcher createDispatcher(ExecutorService service, boolean scansNetworkChanges) { + when(context.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(connectivityManager); + when(context.checkCallingOrSelfPermission(anyString())).thenReturn( + scansNetworkChanges ? PERMISSION_GRANTED : PERMISSION_DENIED); + return new Dispatcher(context, service, mainThreadHandler, downloader, cache, stats); + } +} From 94339fc5e136ee9e9c1f6045146d2e1a18364a75 Mon Sep 17 00:00:00 2001 From: Hannes Dorfmann Date: Wed, 18 Jun 2014 12:51:10 +0200 Subject: [PATCH 03/10] More unit testing --- .../picasso/DispatchingQueueTest.java | 89 ++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/picasso/src/test/java/com/squareup/picasso/DispatchingQueueTest.java b/picasso/src/test/java/com/squareup/picasso/DispatchingQueueTest.java index c590abfe2e..ba2952e0cb 100644 --- a/picasso/src/test/java/com/squareup/picasso/DispatchingQueueTest.java +++ b/picasso/src/test/java/com/squareup/picasso/DispatchingQueueTest.java @@ -17,10 +17,14 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static com.squareup.picasso.TestUtils.BITMAP_1; +import static com.squareup.picasso.TestUtils.URI_1; import static com.squareup.picasso.TestUtils.URI_KEY_1; +import static com.squareup.picasso.TestUtils.mockAction; import static com.squareup.picasso.TestUtils.mockHunter; +import static com.squareup.picasso.TestUtils.mockTarget; import static org.fest.assertions.api.Assertions.assertThat; import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; @@ -126,13 +130,96 @@ public void interruptingContinuing(){ assertThat(dispatchingQueue.isDispatchingEnabled()).isFalse(); } - dispatcher.continueDispatching(); + if (interruptions == 1) { + interruptions --; + dispatcher.continueDispatching(); + } assertThat(dispatchingQueue.isDispatchingEnabled()).isTrue(); } + private DispatchingQueue.DispatchJob getJobFromQueue(BitmapHunter hunter){ + for (DispatchingQueue.DispatchJob job : dispatchingQueue.jobQueue){ + if (job.getBitmapHunter() == hunter){ + return job; + } + } + + return null; + } + + @Test + public void addingRemovingToQueue(){ + + + int tests = new Random().nextInt(10)+5; + + for (int j = 0; j< tests; j++) { + + // Interrupt dispatching + dispatcher.interruptDispatching(); + assertThat(dispatchingQueue.isDispatchingEnabled()).isFalse(); + + + for (int i = 1; i <= 10; i++) { + BitmapHunter hunter = mockHunter(URI_KEY_1, BITMAP_1, false); + if (i % 2 == 0) { + dispatcher.dispatchComplete(hunter); + } else { + dispatcher.dispatchFailed(hunter); + } + assertThat(dispatchingQueue.hunterMap).hasSize(i); + assertThat(dispatchingQueue.jobQueue).hasSize(i); + + assertThat(dispatchingQueue.hunterMap).containsKey(hunter); + assertThat(dispatchingQueue.hunterMap.get(hunter)).isNotNull(); + + DispatchingQueue.DispatchJob job = getJobFromQueue(hunter); + assertThat(job).isNotNull(); + assertThat(dispatchingQueue.hunterMap.get(hunter)).isSameAs(job); + + + } + + // Continue Dispatching + dispatcher.continueDispatching(); + assertThat(dispatchingQueue.isDispatchingEnabled()).isTrue(); + assertThat(dispatchingQueue.hunterMap).isEmpty(); + assertThat(dispatchingQueue.jobQueue).isEmpty(); + + + } + + } + + @Test + public void performCancel(){ + + Target target = mockTarget(); + Action action = mockAction(URI_KEY_1, URI_1, target); + BitmapHunter hunter = mockHunter(URI_KEY_1, BITMAP_1, false); + hunter.attach(action); + when(hunter.cancel()).thenReturn(true); + dispatcher.hunterMap.put(URI_KEY_1, hunter); + dispatcher.failedActions.put(target, action); + dispatcher.interruptDispatching(); + assertThat(dispatchingQueue.isDispatchingEnabled()).isFalse(); + dispatchingQueue.dispatchComplete(hunter); + assertThat(dispatchingQueue.hunterMap).containsKey(hunter); + assertThat(getJobFromQueue(hunter)).isNotNull(); + dispatcher.performCancel(action); + verify(hunter).detach(action); + verify(hunter).cancel(); + assertThat(dispatcher.hunterMap).isEmpty(); + assertThat(dispatcher.failedActions).isEmpty(); + assertThat(dispatchingQueue.hunterMap).isEmpty(); + assertThat(dispatchingQueue.jobQueue).isEmpty(); + + } + + private Dispatcher createDispatcher() { return createDispatcher(service); From 0419d259e2df8e8278535e58d360394ab4a14ca9 Mon Sep 17 00:00:00 2001 From: Hannes Dorfmann Date: Wed, 18 Jun 2014 13:50:11 +0200 Subject: [PATCH 04/10] Samples with Scroll Listener --- picasso-sample/AndroidManifest.xml | 3 + .../sample_gridview_activity_scrolling.xml | 27 ++++ .../sample_list_detail_list_scrolling.xml | 24 ++++ .../example/picasso/PicassoSampleAdapter.java | 2 + .../picasso/SampleGridViewActivity.java | 5 +- .../picasso/SampleGridViewAdapter.java | 27 ++-- .../SampleGridViewScrollingActivity.java | 43 +++++++ .../picasso/SampleListDetailActivity.java | 5 +- .../picasso/SampleListDetailAdapter.java | 21 +++- .../SampleListDetailScrollingActivity.java | 118 ++++++++++++++++++ 10 files changed, 257 insertions(+), 18 deletions(-) create mode 100644 picasso-sample/res/layout/sample_gridview_activity_scrolling.xml create mode 100644 picasso-sample/res/layout/sample_list_detail_list_scrolling.xml create mode 100644 picasso-sample/src/main/java/com/example/picasso/SampleGridViewScrollingActivity.java create mode 100644 picasso-sample/src/main/java/com/example/picasso/SampleListDetailScrollingActivity.java diff --git a/picasso-sample/AndroidManifest.xml b/picasso-sample/AndroidManifest.xml index 502d74701e..09f006282c 100644 --- a/picasso-sample/AndroidManifest.xml +++ b/picasso-sample/AndroidManifest.xml @@ -34,9 +34,12 @@ + + + diff --git a/picasso-sample/res/layout/sample_gridview_activity_scrolling.xml b/picasso-sample/res/layout/sample_gridview_activity_scrolling.xml new file mode 100644 index 0000000000..3c14f7f74f --- /dev/null +++ b/picasso-sample/res/layout/sample_gridview_activity_scrolling.xml @@ -0,0 +1,27 @@ + + + + + + + +