如何在最后一个订阅者取消订阅后延迟拆除共享的,无限的Observable

时间:2016-04-13 11:30:44

标签: android rx-java rx-android

我们在Android App中使用mutliple服务。这些服务将其数据提供为无限Observables,通常通过组合Observables其他服务来构建。 这些Observables的构建成本很高。此外,服务通常在多个地方消费,因此他们的Observable应该在订阅者之间共享。

实施例

  • LocationService,提供无限Observable<Location>,发出当前位置
  • ReminderService,提供无限Observable<List<Reminder>>,在数据集中的每次更改后发出所有存储的提醒列表
  • LocationAwareReminderService,通过Observable<List<Reminders>>前两项服务的Observable.combineLatest提供无限Observables个附近提醒

第一种方法:内部BehaviorSubjects as cache

每个服务都会将消费的Observables组合在一起,并将其内部BehaviorSubject订阅到生成的Feed中。然后,消费者可以订阅此BehaviorSubject。 例如LocationAwareReminderService

public class LocationAwareReminderService {

    Observable<List<Reminder>> feed;

    public LocationAwareReminderService(ReminderService reminderService, LocationService locationService) {
        BehaviorSubject<List<Reminder>> cache = BehaviorSubject.create();
        Observable.combineLatest(reminderService.getFeed(), locationService.getFeed(), new Func2<List<Reminder>, Location, List<Reminder>>() {
            @Override
            public List<Reminder> call(List<Reminder> reminders, Location location) {
                return calculateNearbyReminders(reminders, location);
            }
        }).subscribe(cache);

        feed = cache.asObservable();
    }

    public Observable<List<Reminder>> getFeed() {
        return feed;
    }
}

缺点:

  • 由于行为主题,提醒服务和locatoinService的提要永远不会被删除。即使没有消费者
  • 如果它们依赖于不断发布新项目的服务(如LocationService),则这尤其成问题
  • 由于构造函数中的订阅(缓存),即使没有订户在场,服务也会开始计算附近的提醒

优势:

  • 结果Feed由所有订阅者共享
  • 因为饲料从未被拆除,短期内没有订户不会使整个管道倒塌

第二种方法:重播(1).refCount()。

public class LocationAwareReminderService {

    Observable<List<Reminder>> feed;

    public LocationAwareReminderService(ReminderService reminderService, LocationService locationService) {
        feed = Observable.combineLatest(reminderService.getFeed(), locationService.getFeed(), new Func2<List<Reminder>, Location, List<Reminder>>() {
            @Override
            public List<Reminder> call(List<Reminder> reminders, Location location) {
                return calculateNearbyReminders(reminders, location);
            }
        }).replay(1).refCount();
    }

    public Observable<List<Reminder>> getFeed() {
        return feed;
    }
}

缺点:

  • 没有Subscriber整个管道崩溃的短期。在下一次订阅期间,需要重建整个管道。
  • Activity A到Activity B的转换,都订阅了LocationAwareReminderService.getFeed(),导致管道完全取消和重建

优势:

  • 在上次Subscriber取消订阅后,LocationAwareReminderService也会取消订阅LocationService.getFeed()reminderService.getFeed() Observables
  • LocationAwareReminderService仅在第一个Subscriber订阅后才开始提供附近的提醒
  • 生成的Feed由所有Subscriber s
  • 共享

第三种方法:使用超时

取消订阅refCount

因此我构建了一个Transformer,使订阅在最后Subscriber取消订阅

之后的一段时间内保持活动状态
public class RxPublishTimeoutCache<T> implements Observable.Transformer<T, T> {

    private long keepAlive;
    private TimeUnit timeUnit;

    public RxPublishTimeoutCache(long keepAlive, TimeUnit timeUnit) {
        this.keepAlive = keepAlive;
        this.timeUnit = timeUnit;
    }

    @Override
    public Observable<T> call(Observable<T> upstream) {

        final Observable<T> sharedUpstream = upstream.replay(1).refCount();

        return Observable.create(new Observable.OnSubscribe<T>() {
            @Override
            public void call(Subscriber<? super T> subscriber) {
                if (subscriber.isUnsubscribed())
                    return;
                // subscribe an empty Subscriber that keeps the subsription of refCount() alive
                final Subscription keepAliveSubscription = sharedUpstream.subscribe(new NopSubscriber<T>());
                // listen to unsubscribe from the subscriber
                subscriber.add(Subscriptions.create(new Action0() {
                    @Override
                    public void call() {
                        // the subscriber unsubscribed
                        Observable.timer(keepAlive, timeUnit).subscribe(new Action1<Long>() {
                            @Override
                            public void call(Long _) {
                                // unsubscribe the keep alive subscription
                                keepAliveSubscription.unsubscribe();
                            }
                        });
                    }
                }));
                sharedUpstream.subscribe(subscriber);
            }
        });
    }

    public class NopSubscriber<T> extends Subscriber<T> {
        @Override
        public void onCompleted() {}
        @Override
        public void onError(Throwable e) {}
        @Override
        public void onNext(T o) {}
    }
}

LocationAwareReminderService使用RxPublishTimeoutCache

public class LocationAwareReminderService {

    Observable<List<Reminder>> feed;

    public LocationAwareReminderService(ReminderService reminderService, LocationService locationService) {
        feed = Observable.combineLatest(reminderService.getFeed(), locationService.getFeed(), new Func2<List<Reminder>, Location, List<Reminder>>() {
            @Override
            public List<Reminder> call(List<Reminder> reminders, Location location) {
                return calculateNearbyReminders(reminders, location);
            }
        }).compose(new RxPublishTimeoutCache<List<Reminder>>(10, TimeUnit.SECONDS));
    }

    public Observable<List<Reminder>> getFeed() {
        return feed;
    }
}

优势:

  • LocationAwareReminderService仅在第一个Subscriber订阅后才开始提供附近的提醒
  • 结果Feed由所有订阅者共享
  • 没有订户的短期没有倒塌整个管道
  • 在规定的时间段内没有订阅后,整个管道将被拆除

缺点:

  • 也许是一些普遍的缺陷?

的问题:

  • 在RxJava中还有其他方法可以实现吗?
  • RxPublishTimeoutCache
  • 是否存在一些常见的设计缺陷
  • 使用RxJava构建此类服务的整体策略是否存在缺陷?

2 个答案:

答案 0 :(得分:2)

我认为这是一个有趣的问题,似乎是一个有用的操作符,因此我在rxjava-extras中创建了var image = new BitmapImage(); using (var stream = await element.RenderToRandomAccessStream()) { image.SetSource(stream); } ImageBrush ib = new ImageBrush(); ib.ImageSource = splitViewBlurImage; ib.Stretch = Stretch.UniformToFill; splitViewSideBarBlur.Source = splitViewBlurImage; splitViewSideBarBlur.VerticalAlignment = VerticalAlignment.Top; var graphicsEffect = new BlendEffect { Background = new ColorSourceEffect() { Name = "Tint", Color = Color.FromArgb(95, 255, 255, 255) }, Foreground = new GaussianBlurEffect() { Name = "Blur", Source = new CompositionEffectSourceParameter("Backdrop"), BlurAmount = (float)20, BorderMode = EffectBorderMode.Hard, } }; var blurEffectFactory = _compositor.CreateEffectFactory(graphicsEffect, new[] { "Blur.BlurAmount", "Tint.Color" }); _brush = blurEffectFactory.CreateBrush(); var destinationBrush = _compositor.CreateBackdropBrush(); _brush.SetSourceParameter("Backdrop", destinationBrush); var blurSprite = _compositor.CreateSpriteVisual(); blurSprite.Size = new Vector2((float)splitViewSideBarBlur.ActualWidth, (float)splitViewSideBarBlur.ActualHeight); blurSprite.Brush = _brush; this.splitViewSideBarBlur.Source = null; ElementCompositionPreview.SetElementChildVisual(this.splitViewSideBarBlur, blurSprite);

Transformers.delayFinalUnsubscribe

它可以在Maven Central的0.7.9.1的rxjava-extras中找到。如果你愿意,可以给它一个旋转,看看是否有任何问题。

答案 1 :(得分:0)

现在,refCount的重载需要超时,而这恰好做到了