使用RxJava管理相同的请求

时间:2015-04-16 18:16:29

标签: android reactive-programming rx-java

假设我有一个fetcher,它从一个单独的线程上的给定链接中获取图像。然后将图像缓存在内存中。一旦图像已经被缓存,提取器将不会重新获取链接。提取器被视为Observable。可能有许多订户要求提取者提供图像。在第一个订阅者订阅了提取者之后,提取者将拍摄网络。但是,如果有第二个订阅者来订阅,那么获取者不应该再拍摄另一个请求,而它之前已经提取了一个请求。之后,如果获取完成,则两个订阅者都将获得该图像。现在,如果有第三个用户,提取器将立即发出图像。

如何使用RxJava方法实现上述方案?我期望利用某种现有的运算符,以更具声明性的方式组合它们,最重要的是,避免同步,锁定和原子的开销。

3 个答案:

答案 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个网络请求,任何用户都会(最终/立即)获得该响应。