假设我有一个fetcher,它从一个单独的线程上的给定链接中获取图像。然后将图像缓存在内存中。一旦图像已经被缓存,提取器将不会重新获取链接。提取器被视为Observable。可能有许多订户要求提取者提供图像。在第一个订阅者订阅了提取者之后,提取者将拍摄网络。但是,如果有第二个订阅者来订阅,那么获取者不应该再拍摄另一个请求,而它之前已经提取了一个请求。之后,如果获取完成,则两个订阅者都将获得该图像。现在,如果有第三个用户,提取器将立即发出图像。
如何使用RxJava方法实现上述方案?我期望利用某种现有的运算符,以更具声明性的方式组合它们,最重要的是,避免同步,锁定和原子的开销。
答案 0 :(得分:2)
问题在于:"假设我有一个提取器,它从一个单独的线程上的给定链接中获取图像。然后,图像将缓存在内存中。"
答案是cache()
运算符:
"记住Observable发出的项目序列,并向未来的订阅者发出相同的序列"
来自:https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators
因此,以下Observable
应该只获取一次图像,无论Subscribers
如何订阅它:
Observable<Bitmap> cachedBitmap = fetchBitmapFrom(url).cache();
修改强>
我认为以下示例证明了上游Observable
只订阅了一次,即使在Observable
发出任何内容之前有多个订阅。对于网络请求也应如此。
package com.example;
import rx.Observable;
import rx.Subscriber;
import rx.schedulers.Schedulers;
public class SimpleCacheTest {
public static void main(String[] args) {
final Observable<Integer> cachedSomething = getSomething().cache();
System.out.println("before first subscription");
cachedSomething.subscribe(new SimpleLoggingSubscriber<Integer>("1"));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("before second subscription");
cachedSomething.subscribe(new SimpleLoggingSubscriber<Integer>("2"));
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("quit");
}
private static class SimpleLoggingSubscriber<T> extends Subscriber<T> {
private final String tag;
public SimpleLoggingSubscriber(final String tag) {
this.tag = tag;
}
@Override
public void onCompleted() {
System.out.println("onCompleted (" + tag + ")");
}
@Override
public void onError(Throwable e) {
System.out.println("onError (" + tag + ")");
}
@Override
public void onNext(T t) {
System.out.println("onNext (" + tag + "): " + t);
}
}
private static Observable<Integer> getSomething() {
return Observable.create(new Observable.OnSubscribe<Integer>(){
@Override
public void call(Subscriber<? super Integer> subscriber) {
System.out.println("going to sleep now...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
subscriber.onNext(1);
subscriber.onCompleted();
}
}).subscribeOn(Schedulers.io());
}
}
输出:
before first subscription
going to sleep now...
before second subscription
onNext (1): 1
onNext (2): 1
onCompleted (1)
onCompleted (2)
quit
答案 1 :(得分:1)
这可以通过ConcurrentMap和AsyncSubject实现:
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.util.concurrent.*;
import javax.imageio.ImageIO;
import rx.*;
import rx.Scheduler.Worker;
import rx.schedulers.Schedulers;
import rx.subjects.AsyncSubject;
public class ObservableImageCache {
final ConcurrentMap<String, AsyncSubject<BufferedImage>> image =
new ConcurrentHashMap<>();
public Observable<BufferedImage> get(String url) {
AsyncSubject<BufferedImage> result = image.get(url);
if (result == null) {
result = AsyncSubject.create();
AsyncSubject<BufferedImage> existing = image.putIfAbsent(url, result);
if (existing == null) {
System.out.println("Debug: Downloading " + url);
AsyncSubject<BufferedImage> a = result;
Worker w = Schedulers.io().createWorker();
w.schedule(() -> {
try {
Thread.sleep(500); // for demo
URL u = new URL(url);
try (InputStream openStream = u.openStream()) {
a.onNext(ImageIO.read(openStream));
}
a.onCompleted();
} catch (IOException | InterruptedException ex) {
a.onError(ex);
} finally {
w.unsubscribe();
}
});
} else {
result = existing;
}
}
return result;
}
public static void main(String[] args) throws Exception {
ObservableImageCache cache = new ObservableImageCache();
CountDownLatch cdl = new CountDownLatch(4);
Observable<BufferedImage> img1 = cache.get("https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/create.png");
System.out.println("Subscribing for IMG1");
img1.subscribe(e -> System.out.println("IMG1: " + e.getWidth() + "x" + e.getHeight()), Throwable::printStackTrace, cdl::countDown);
Thread.sleep(500);
Observable<BufferedImage> img2 = cache.get("https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/create.png");
System.out.println("Subscribing for IMG2");
img2.subscribe(e -> System.out.println("IMG2: " + e.getWidth() + "x" + e.getHeight()), Throwable::printStackTrace, cdl::countDown);
Observable<BufferedImage> img3 = cache.get("https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/amb.png");
Observable<BufferedImage> img4 = cache.get("https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/amb.png");
Thread.sleep(500);
System.out.println("Subscribing for IMG3");
img3.subscribe(e -> System.out.println("IMG3: " + e.getWidth() + "x" + e.getHeight()), Throwable::printStackTrace, cdl::countDown);
Thread.sleep(1000);
System.out.println("-> Should be immediate: ");
System.out.println("Subscribing for IMG4");
img4.subscribe(e -> System.out.println("IMG4: " + e.getWidth() + "x" + e.getHeight()), Throwable::printStackTrace, cdl::countDown);
cdl.await();
}
}
我使用ConcurrentMap的putIfAbsent确保只为新网址触发了一次下载;其他人都会收到相同的AsyncSubject,他们可以在这些对象上等待。并在此之后立即获取数据。通常,您希望使用自定义计划程序来限制并发下载的数量。
答案 2 :(得分:0)
查看ConnectableObservable
和.replay()
方法。
我目前正在使用这是我的片段来处理方向变化:
Fragment's onCreate:
ConnectableObservable<MyThing> connectableObservable =
retrofitService.fetchMyThing()
.map(...)
.replay();
connectableObservable.connect(); // this starts the actual network call
片段的onCreateView:
Subscription subscription = connectableObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mything -> dosomething());
我只发出1个网络请求,任何用户都会(最终/立即)获得该响应。