diff options
| author | Samuel Tardieu <sam@rfc1149.net> | 2014-01-12 16:15:29 +0100 |
|---|---|---|
| committer | Samuel Tardieu <sam@rfc1149.net> | 2014-01-12 23:21:55 +0100 |
| commit | 7fe17b6b441e145e7e4c46f0aed83b86a00d4e57 (patch) | |
| tree | 29136ac2e174221f8ab65bb4e7874e6a2de6f1f2 | |
| parent | df177f0e15f4a8fa3dd378648477200596306be9 (diff) | |
| download | cgeo-7fe17b6b441e145e7e4c46f0aed83b86a00d4e57.zip cgeo-7fe17b6b441e145e7e4c46f0aed83b86a00d4e57.tar.gz cgeo-7fe17b6b441e145e7e4c46f0aed83b86a00d4e57.tar.bz2 | |
When storing a cache, download images concurrently
Up to 5 downloads can happen simultaneously. Also, those downloads are
executed concurrently to static map requests if any.
| -rw-r--r-- | main/src/cgeo/geocaching/Geocache.java | 2 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/network/HtmlImage.java | 64 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/ui/ImagesList.java | 32 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/utils/CancellableHandler.java | 16 |
4 files changed, 85 insertions, 29 deletions
diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java index 30ab55f..42b8f0a 100644 --- a/main/src/cgeo/geocaching/Geocache.java +++ b/main/src/cgeo/geocaching/Geocache.java @@ -1643,6 +1643,8 @@ public class Geocache implements ICache, IWaypoint { StaticMapsProvider.downloadMaps(cache); + imgGetter.waitForBackgroundLoading(handler); + if (handler != null) { handler.sendMessage(Message.obtain()); } diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java index 1f50bd7..774dbf6 100644 --- a/main/src/cgeo/geocaching/network/HtmlImage.java +++ b/main/src/cgeo/geocaching/network/HtmlImage.java @@ -6,6 +6,7 @@ import cgeo.geocaching.compatibility.Compatibility; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.files.LocalStorage; import cgeo.geocaching.list.StoredList; +import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.FileUtils; import cgeo.geocaching.utils.ImageUtils; import cgeo.geocaching.utils.Log; @@ -15,6 +16,16 @@ import ch.boye.httpclientandroidlib.androidextra.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.Nullable; +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Scheduler; +import rx.Subscription; +import rx.concurrency.Schedulers; +import rx.subjects.PublishSubject; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Func1; import android.content.res.Resources; import android.graphics.Bitmap; @@ -32,6 +43,11 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public class HtmlImage implements Html.ImageGetter { @@ -65,6 +81,14 @@ public class HtmlImage implements Html.ImageGetter { final private int maxHeight; final private Resources resources; + // Background loading + final private PublishSubject<Observable<String>> loading = PublishSubject.create(); + final Observable<String> waitForEnd = Observable.merge(loading).publish().refCount(); + final CompositeSubscription subscription = new CompositeSubscription(waitForEnd.subscribe()); + final private Scheduler downloadScheduler = Schedulers.executor(new ThreadPoolExecutor(5, 5, 5, TimeUnit.SECONDS, + new LinkedBlockingQueue<Runnable>())); + final private Set<String> downloading = new HashSet<String>(); + public HtmlImage(final String geocode, final boolean returnErrorImage, final int listId, final boolean onlySave) { this.geocode = geocode; this.returnErrorImage = returnErrorImage; @@ -84,6 +108,46 @@ public class HtmlImage implements Html.ImageGetter { @Nullable @Override public BitmapDrawable getDrawable(final String url) { + if (!onlySave) { + return loadDrawable(url); + } else { + synchronized(downloading) { + if (!downloading.contains(url)) { + loading.onNext(fetchDrawable(url).map(new Func1<BitmapDrawable, String>() { + @Override + public String call(final BitmapDrawable bitmapDrawable) { + return url; + } + })); + downloading.add(url); + } + return null; + } + } + } + + public Observable<BitmapDrawable> fetchDrawable(final String url) { + return Observable.create(new OnSubscribeFunc<BitmapDrawable>() { + @Override + public Subscription onSubscribe(final Observer<? super BitmapDrawable> observer) { + if (!subscription.isUnsubscribed()) { + observer.onNext(loadDrawable(url)); + } + observer.onCompleted(); + return Subscriptions.empty(); + } + }).subscribeOn(downloadScheduler); + } + + public void waitForBackgroundLoading(@Nullable final CancellableHandler handler) { + if (handler != null) { + handler.unsubscribeIfCancelled(subscription); + } + loading.onCompleted(); + waitForEnd.toBlockingObservable().lastOrDefault(null); + } + + private BitmapDrawable loadDrawable(final String url) { // Reject empty and counter images URL if (StringUtils.isBlank(url) || isCounter(url)) { return getTransparent1x1Image(resources); diff --git a/main/src/cgeo/geocaching/ui/ImagesList.java b/main/src/cgeo/geocaching/ui/ImagesList.java index fb2ac15..f564583 100644 --- a/main/src/cgeo/geocaching/ui/ImagesList.java +++ b/main/src/cgeo/geocaching/ui/ImagesList.java @@ -9,16 +9,8 @@ import cgeo.geocaching.utils.Log; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; - -import rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; -import rx.Scheduler; -import rx.Subscription; import rx.android.concurrency.AndroidSchedulers; -import rx.concurrency.Schedulers; import rx.subscriptions.CompositeSubscription; -import rx.subscriptions.Subscriptions; import rx.util.functions.Action1; import android.app.Activity; @@ -46,9 +38,6 @@ import java.io.FileOutputStream; import java.util.Collection; import java.util.LinkedList; import java.util.List; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; public class ImagesList { @@ -82,9 +71,6 @@ public class ImagesList { private final String geocode; private LinearLayout imagesView; - private Scheduler downloadScheduler = Schedulers.executor(new ThreadPoolExecutor(5, 5, 5, TimeUnit.SECONDS, - new LinkedBlockingQueue<Runnable>())); - public ImagesList(final Activity activity, final String geocode) { this.activity = activity; this.geocode = geocode; @@ -92,9 +78,10 @@ public class ImagesList { } public void loadImages(final View parentView, final List<Image> images, final boolean offline) { - imagesView = (LinearLayout) parentView.findViewById(R.id.spoiler_list); + final HtmlImage imgGetter = new HtmlImage(geocode, true, offline ? StoredList.STANDARD_LIST_ID : StoredList.TEMPORARY_LIST_ID, false); + for (final Image img : images) { final LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, null); assert(rowView != null); @@ -110,8 +97,7 @@ public class ImagesList { descView.setVisibility(View.VISIBLE); } - subscriptions.add(loadImage(img, offline) - .subscribeOn(downloadScheduler) + subscriptions.add(imgGetter.fetchDrawable(img.getUrl()) .observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<BitmapDrawable>() { @Override public void call(final BitmapDrawable image) { @@ -123,18 +109,6 @@ public class ImagesList { } } - private Observable<BitmapDrawable> loadImage(final Image img, final boolean offline) { - return Observable.create(new OnSubscribeFunc<BitmapDrawable>() { - @Override - public Subscription onSubscribe(final Observer<? super BitmapDrawable> observer) { - final HtmlImage imgGetter = new HtmlImage(geocode, true, offline ? StoredList.STANDARD_LIST_ID : StoredList.TEMPORARY_LIST_ID, false); - observer.onNext(imgGetter.getDrawable(img.getUrl())); - observer.onCompleted(); - return Subscriptions.empty(); - } - }); - } - private void display(final BitmapDrawable image, final Image img, final LinearLayout view) { if (image != null) { bitmaps.add(image.getBitmap()); diff --git a/main/src/cgeo/geocaching/utils/CancellableHandler.java b/main/src/cgeo/geocaching/utils/CancellableHandler.java index cb4b9db..01fb568 100644 --- a/main/src/cgeo/geocaching/utils/CancellableHandler.java +++ b/main/src/cgeo/geocaching/utils/CancellableHandler.java @@ -2,6 +2,9 @@ package cgeo.geocaching.utils; import cgeo.geocaching.CgeoApplication; +import rx.Subscription; +import rx.subscriptions.CompositeSubscription; + import android.os.Handler; import android.os.Message; @@ -13,6 +16,7 @@ public abstract class CancellableHandler extends Handler { protected static final int UPDATE_LOAD_PROGRESS_DETAIL = 42186; private volatile boolean cancelled = false; + private static CompositeSubscription subscriptions = new CompositeSubscription(); private static class CancelHolder { final Object payload; @@ -30,6 +34,7 @@ public abstract class CancellableHandler extends Handler { if (message.obj instanceof CancelHolder) { cancelled = true; + subscriptions.unsubscribe(); handleCancel(((CancelHolder) message.obj).payload); } else { handleRegularMessage(message); @@ -37,6 +42,17 @@ public abstract class CancellableHandler extends Handler { } /** + * Add a subscription to the list of subscriptions to be subscribed at cancellation time. + */ + final public void unsubscribeIfCancelled(final Subscription subscription) { + subscriptions.add(subscription); + if (cancelled) { + // Protect against race conditions + subscriptions.unsubscribe(); + } + } + + /** * Handle a non-cancel message.<br> * Subclasses must implement this to handle messages. * |
