diff options
| author | Samuel Tardieu <sam@rfc1149.net> | 2015-01-13 16:06:04 +0100 |
|---|---|---|
| committer | Samuel Tardieu <sam@rfc1149.net> | 2015-01-13 16:06:04 +0100 |
| commit | 1d216b5056c43d8189c752c8c51bba158ccec149 (patch) | |
| tree | 3016fa1b4be3426763afa7e0d51fbd3376594159 /main | |
| parent | 8820b5b5cf40d27007dae7c2a14585827e004fba (diff) | |
| parent | 8cf0fc31b90907eabad3e82bb003da6d80f43866 (diff) | |
| download | cgeo-1d216b5056c43d8189c752c8c51bba158ccec149.zip cgeo-1d216b5056c43d8189c752c8c51bba158ccec149.tar.gz cgeo-1d216b5056c43d8189c752c8c51bba158ccec149.tar.bz2 | |
Merge branch 'issue-4481' into upstream
Diffstat (limited to 'main')
| -rw-r--r-- | main/src/cgeo/geocaching/network/HtmlImage.java | 11 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/utils/ImageUtils.java | 76 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/utils/RxUtils.java | 8 |
3 files changed, 80 insertions, 15 deletions
diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java index 97e0e45..fe67af4 100644 --- a/main/src/cgeo/geocaching/network/HtmlImage.java +++ b/main/src/cgeo/geocaching/network/HtmlImage.java @@ -44,6 +44,8 @@ import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.util.HashMap; +import java.util.Map; /** * All-purpose image getter that can also be used as a ImageGetter interface when displaying caches. @@ -80,6 +82,7 @@ public class HtmlImage implements Html.ImageGetter { final private int maxHeight; final private Resources resources; protected final TextView view; + final private Map<String, BitmapDrawable> cache = new HashMap<>(); final private ObservableCache<String, BitmapDrawable> observableCache = new ObservableCache<>(new Func1<String, Observable<BitmapDrawable>>() { @Override @@ -162,6 +165,9 @@ public class HtmlImage implements Html.ImageGetter { @Nullable @Override public BitmapDrawable getDrawable(final String url) { + if (cache.containsKey(url)) { + return cache.get(url); + } final Observable<BitmapDrawable> drawable = fetchDrawable(url); if (onlySave) { loading.onNext(drawable.map(new Func1<BitmapDrawable, String>() { @@ -170,9 +176,12 @@ public class HtmlImage implements Html.ImageGetter { return url; } })); + cache.put(url, null); return null; } - return view == null ? drawable.toBlocking().lastOrDefault(null) : getContainerDrawable(drawable); + final BitmapDrawable result = view == null ? drawable.toBlocking().lastOrDefault(null) : getContainerDrawable(drawable); + cache.put(url, result); + return result; } protected BitmapDrawable getContainerDrawable(final Observable<BitmapDrawable> drawable) { diff --git a/main/src/cgeo/geocaching/utils/ImageUtils.java b/main/src/cgeo/geocaching/utils/ImageUtils.java index e2ae7f5..9011ab9 100644 --- a/main/src/cgeo/geocaching/utils/ImageUtils.java +++ b/main/src/cgeo/geocaching/utils/ImageUtils.java @@ -7,11 +7,14 @@ import cgeo.geocaching.compatibility.Compatibility; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import rx.Observable; +import rx.Scheduler.Worker; import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action0; import rx.functions.Action1; import android.content.res.Resources; @@ -39,11 +42,14 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Locale; import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; public final class ImageUtils { private static final int[] ORIENTATIONS = new int[] { @@ -340,17 +346,33 @@ public final class ImageUtils { * Container which can hold a drawable (initially an empty one) and get a newer version when it * becomes available. It also invalidates the view the container belongs to, so that it is * redrawn properly. + * <p/> + * When a new version of the drawable is available, it is put into a queue and, if needed (no other elements + * waiting in the queue), a refresh is launched on the UI thread. This refresh will empty the queue (including + * elements arrived in the meantime) and ensures that the view is uploaded only once all the queued requests have + * been handled. */ public static class ContainerDrawable extends BitmapDrawable implements Action1<Drawable> { + final private static Object lock = new Object(); // Used to lock the queue to determine if a refresh needs to be scheduled + final private static LinkedBlockingQueue<ImmutablePair<ContainerDrawable, Drawable>> REDRAW_QUEUE = new LinkedBlockingQueue<>(); + final private static Set<TextView> VIEWS = new HashSet<>(); // Modified only on the UI thread + final private static Worker UI_WORKER = AndroidSchedulers.mainThread().createWorker(); + final private static Action0 REDRAW_QUEUED_DRAWABLES = new Action0() { + @Override + public void call() { + redrawQueuedDrawables(); + } + }; + private Drawable drawable; - final private TextView view; + final protected TextView view; @SuppressWarnings("deprecation") public ContainerDrawable(@NonNull final TextView view, final Observable<? extends Drawable> drawableObservable) { this.view = view; drawable = null; setBounds(0, 0, 0, 0); - updateFrom(drawableObservable); + drawableObservable.subscribe(this); } @Override @@ -361,32 +383,64 @@ public final class ImageUtils { } @Override - public void call(final Drawable newDrawable) { + public final void call(final Drawable newDrawable) { + final boolean needsRedraw; + synchronized (lock) { + // Check for emptyness inside the call to match the behaviour in redrawQueuedDrawables(). + needsRedraw = REDRAW_QUEUE.isEmpty(); + REDRAW_QUEUE.add(ImmutablePair.of(this, newDrawable)); + } + if (needsRedraw) { + UI_WORKER.schedule(REDRAW_QUEUED_DRAWABLES); + } + } + + /** + * Update the container with the new drawable. Called on the UI thread. + * + * @param newDrawable the new drawable + * @return the view to update + */ + protected TextView updateDrawable(final Drawable newDrawable) { setBounds(0, 0, newDrawable.getIntrinsicWidth(), newDrawable.getIntrinsicHeight()); drawable = newDrawable; - view.setText(view.getText()); + return view; } - public final void updateFrom(final Observable<? extends Drawable> drawableObservable) { - drawableObservable.observeOn(AndroidSchedulers.mainThread()).subscribe(this); + private static void redrawQueuedDrawables() { + if (!REDRAW_QUEUE.isEmpty()) { + // Add a small margin so that drawables arriving between the beginning of the allocation and the draining + // of the queue might be absorbed without reallocation. + final ArrayList<ImmutablePair<ContainerDrawable, Drawable>> toRedraw = new ArrayList<>(REDRAW_QUEUE.size() + 16); + synchronized (lock) { + // Empty the queue inside the lock to match the check done in call(). + REDRAW_QUEUE.drainTo(toRedraw); + } + for (final ImmutablePair<ContainerDrawable, Drawable> redrawable : toRedraw) { + VIEWS.add(redrawable.left.updateDrawable(redrawable.right)); + } + for (final TextView view : VIEWS) { + view.setText(view.getText()); + } + VIEWS.clear(); + } } + } /** * Image that automatically scales to fit a line of text in the containing {@link TextView}. */ public final static class LineHeightContainerDrawable extends ContainerDrawable { - private final TextView view; - public LineHeightContainerDrawable(@NonNull final TextView view, final Observable<? extends Drawable> drawableObservable) { super(view, drawableObservable); - this.view = view; } @Override - public void call(final Drawable newDrawable) { - super.call(newDrawable); + protected TextView updateDrawable(final Drawable newDrawable) { + super.updateDrawable(newDrawable); setBounds(ImageUtils.scaleImageToLineHeight(newDrawable, view)); + return view; } } diff --git a/main/src/cgeo/geocaching/utils/RxUtils.java b/main/src/cgeo/geocaching/utils/RxUtils.java index af58214..280575b 100644 --- a/main/src/cgeo/geocaching/utils/RxUtils.java +++ b/main/src/cgeo/geocaching/utils/RxUtils.java @@ -194,7 +194,7 @@ public class RxUtils { } /** - * Cache observables so that every key is associated to only one of them. + * Cache the last value of observables so that every key is associated to only one of them. * * @param <K> the type of the key * @param <V> the type of the value @@ -215,7 +215,9 @@ public class RxUtils { /** * Get the observable corresponding to a key. If the key has not already been - * seen, the function passed to the constructor will be called to build the observable. + * seen, the function passed to the constructor will be called to build the observable + * <p/> + * If the observable has already emitted values, only the last one will be remembered. * * @param key the key * @return the observable corresponding to the key @@ -224,7 +226,7 @@ public class RxUtils { if (cached.containsKey(key)) { return cached.get(key); } - final Observable<V> value = func.call(key).share(); + final Observable<V> value = func.call(key).replay(1).refCount(); cached.put(key, value); return value; } |
