使用RXJava进行缓存处理

时间:2015-04-28 16:01:24

标签: java android design-patterns reactive-programming rx-java

我试图用rxJava实现这个工作流程,但我确定我是否误用或做错了。

  • 用户要求登录
  • 如果loginResult在缓存中可用,那么" emit"缓存的LoginResult
  • 如果一切成功,
  • 实际上会对webservice执行请求并缓存结果
  • 如果发生错误,最多重试3次,如果有第4次,则清除缓存。

这是我的完整代码片段。

public class LoginTask extends BaseBackground<LoginResult> {
  private static CachedLoginResult cachedLoginResult = new CachedLoginResult();
  private XMLRPCClient xmlrpcClient;
  private UserCredentialsHolder userCredentialsHolder;

  @Inject
  public LoginTask(XMLRPCClient client, UserCredentialsHolder userCredentialsHolder) {
    this.xmlrpcClient = client;
    this.userCredentialsHolder = userCredentialsHolder;
  }

  @Override
  public LoginResult performRequest() throws Exception {
    return UserApi.login(
        xmlrpcClient,
        userCredentialsHolder.getUserName(),
        userCredentialsHolder.getPlainPassword());


  }

  @Override
  public Observable<LoginResult> getObservable() {
    return cachedLoginResult.getObservable()
        .onErrorResumeNext(
            Observable.create(
                ((Observable.OnSubscribe<LoginResult>) subscriber -> {
                  try {
                    if (!subscriber.isUnsubscribed()) {
                      subscriber.onNext(performRequest()); // actually performRequest
                    }
                    subscriber.onCompleted();
                  } catch (Exception e) {
                    subscriber.onError(e);
                  }
                })
            )
                .doOnNext(cachedLoginResult::setLoginResult)
                .retry((attempts, t) -> attempts < 3)
                .doOnError(throwable -> cachedLoginResult.purgeCache())
        );
  }


  private static class CachedLoginResult {
    private LoginResult lr = null;
    private long when = 0;

    private CachedLoginResult() {
    }

    public boolean hasCache() {
      return lr != null && when + TimeUnit.MILLISECONDS.convert(30, TimeUnit.MINUTES) > System.currentTimeMillis();
    }

    public void setLoginResult(LoginResult lr) {
      if (lr != null) {
          this.lr = lr;
          this.when = System.currentTimeMillis();
      }
    }

    public void purgeCache() {
      this.lr = null;
      this.when = 0;
    }

    public Observable<LoginResult> getObservable() {
      return Observable.create(new Observable.OnSubscribe<LoginResult>() {
        @Override
        public void call(Subscriber<? super LoginResult> subscriber) {
          if (!subscriber.isUnsubscribed()) {
            if (hasCache()) {
              subscriber.onNext(lr);
              subscriber.onCompleted();
            } else {
              subscriber.onError(new RuntimeException("No cache"));
            }
          }
        }
      });
    }
  }
}

因为我无法找到任何类似的例子,所以我开始玩#34;与rxjava仅在1天前,我不确定我的实施。

感谢您的时间。

2 个答案:

答案 0 :(得分:3)

我认为这段代码没问题,干得好!)

您在Observable.create中使用LoginTask是正确的,因为否则调用的结果可以在内部缓存,然后retry无济于事......

我认为这对于CachedLoginResult Observable来说是不必要的。在这里,您可以使用Observable.justObservable.error实用程序方法简化代码,例如:

public Observable<LoginResult> getObservable() {
  if (hasCache()) {
      return Observable.just(lr);
  } else {
      return Observable.error(new RuntimeException("No cache"));
  }
}

注意:just存储您告诉它在内部发出的值,因此重新订阅将始终生成此值。这就是我上面提到的,例如,你不应该Observable.just(performRequest()).retry(3),因为performRequest只会被调用一次。

答案 1 :(得分:0)

如果我理解正确,您希望执行一次登录并以被动方式缓存结果?如果是这样,这里有一个例子我将如何做到这一点:

import java.util.concurrent.ThreadLocalRandom;

import rx.*;
import rx.schedulers.Schedulers;
import rx.subjects.AsyncSubject;


public class CachingLogin {
    static class LoginResult {

    }
    /** Guarded by this. */
    AsyncSubject<LoginResult> cache;
    public Observable<LoginResult> login(String username, String password) {
        AsyncSubject<LoginResult> c;
        boolean doLogin = false;
        synchronized (this) {
            if (cache == null || cache.hasThrowable()) {
                cache = AsyncSubject.create();
                doLogin = true;
            }
            c = cache;
        }
        if (doLogin) {
            Observable.just(1).subscribeOn(Schedulers.io())
            .map(v -> loginAPI(username, password))
            .retry(3).subscribe(c);
        }
        return c;
    }
    public void purgeCache() {
        synchronized (this) {
            cache = null;
        }
    }
    static LoginResult loginAPI(String username, String password) {
        if (ThreadLocalRandom.current().nextDouble() < 0.3) {
            throw new RuntimeException("Failed");
        }
        return new LoginResult();
    }
}