Android:使用Retrofit轮询服务器

时间:2015-02-06 15:57:03

标签: android rest polling retrofit rx-java

我在Android上构建了一个2人游戏。游戏以转向方式工作,因此玩家1等待玩家2输入,反之亦然。我有一个网络服务器,我使用Slim框架运行API。在客户端我使用Retrofit。所以在客户端我想每隔X秒轮询我的网络服务器(我知道它不是最好的方法)来检查是否有来自玩家2的输入,如果是,则更改UI(游戏板)。

处理Retrofit我遇到了RxJava。我的问题是弄清楚我是否需​​要使用RxJava?如果是,是否有任何非常简单的例子用于改造轮询? (因为我只发送了几个键/值对)如果不是如何用改造来做呢?

我在这里找到this帖子,但它也没有帮助我,因为我还不知道我是否需要Retrofit + RxJava,有没有更简单的方法?

2 个答案:

答案 0 :(得分:15)

假设你为Retrofit定义的接口包含这样的方法:

public Observable<GameState> loadGameState(@Query("id") String gameId);

改造方法可以用以下三种方式之一来定义:

1。)一个简单的同步:

public GameState loadGameState(@Query("id") String gameId);

2。)采用Callback进行异步处理的那个:

public void loadGameState(@Query("id") String gameId, Callback<GameState> callback);

3。)和返回rxjava Observable的那个,见上文。我想如果你打算将rtrjit与rxjava一起使用,那么使用这个版本是最有意义的。

这样你就可以直接使用Observable作为单个请求:

mApiService.loadGameState(mGameId)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<GameState>() {

    @Override
    public void onNext(GameState gameState) {
        // use the current game state here
    }

    // onError and onCompleted are also here
});

如果您想使用timer()interval()版本来反复轮询服务器:

Observable.timer(0, 2000, TimeUnit.MILLISECONDS)
.flatMap(mApiService.loadGameState(mGameId))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<GameState>() {

    @Override
    public void onNext(GameState gameState) {
        // use the current game state here
    }

    // onError and onCompleted are also here
}).

请务必注意,我在这里使用的是flatMap而不是map - 这是因为loadGameState(mGameId)的返回值本身就是Observable

但您在更新中使用的版本也应该有效:

Observable.interval(2, TimeUnit.SECONDS, Schedulers.io())
.map(tick -> Api.ReceiveGameTurn())
.doOnError(err -> Log.e("Polling", "Error retrieving messages" + err))
.retry()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(sub);

也就是说,如果ReceiveGameTurn()与我上面的1.)同步定义,则可以使用map代替flatMap

在这两种情况下,onNext的{​​{1}}将每两秒钟从服务器调用最新的游戏状态。您可以通过在Subscriber之前插入take(1)来一个接一个地处理它们,将发射限制为单个项目。

但是,关于第一个版本:单个网络错误将首先发送到subscribe(),然后Observable将停止发出更多项目,使订阅者无用而无需输入(请记住,onError只能被召唤一次)。要解决此问题,您可以使用rxjava的任何onError方法将故障“重定向”到onNext。

例如:

onError*

这将每两秒钟: *使用Retrofit从服务器获取当前游戏状态 *过滤掉无效的 *采取第一个有效的 *和取消订阅

如果出现错误: *它将在Observable.timer(0, 2000, TimeUnit.MILLISECONDS) .flatMap(new Func1<Long, Observable<GameState>>(){ @Override public Observable<GameState> call(Long tick) { return mApiService.loadGameState(mGameId) .doOnError(err -> Log.e("Polling", "Error retrieving messages" + err)) .onErrorResumeNext(new Func1<Throwable, Observable<GameState>(){ @Override public Observable<GameState> call(Throwable throwable) { return Observable.emtpy()); } }); } }) .filter(/* check if it is a valid new game state */) .take(1) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<GameState>() { @Override public void onNext(GameState gameState) { // use the current game state here } // onError and onCompleted are also here }). 中打印错误消息 *并忽略错误:doOnNext将“消耗”onErrorResumeNext - 事件(即不会调用onError的{​​{1}})并将其替换为空( Subscriber)。

并且,关于第二个版本:如果网络错误onError将立即重新订阅该间隔 - 并且由于Observable.empty()在订阅后立即发出第一个整数,下一个请求将立即发送,也是 - 而不是在你可能想要的3秒之后......

最后注意事项:此外,如果您的游戏状态非常大,您也可以先轮询服务器,询问是否有新状态,只有在肯定答案的情况下重新加载新游戏状态。

如果您需要更详细的示例,请询问。

更新:我已经重写了这篇文章的部分内容并在其间添加了更多信息。

更新2 :我已使用retry添加了错误处理的完整示例。

答案 1 :(得分:1)

谢谢,我终于根据我在问题中提到的post以类似的方式制作了它。这是我现在的代码:

Subscriber sub =  new Subscriber<Long>() {
        @Override
        public void onNext(Long _EmittedNumber)
        {
            GameTurn Turn =  Api.ReceiveGameTurn(mGameInfo.GetGameID(), mGameInfo.GetPlayerOneID());
            Log.d("Polling", "onNext: GameID - " + Turn.GetGameID());
        }

        @Override
        public void onCompleted() {
            Log.d("Polling", "Completed!");
        }

        @Override
        public void onError(Throwable e) {
            Log.d("Polling", "Error: " + e);
        }
    };

    Observable.interval(3, TimeUnit.SECONDS, Schedulers.io())
            // .map(tick -> Api.ReceiveGameTurn())
            // .doOnError(err -> Log.e("Polling", "Error retrieving messages" + err))
            .retry()
            .subscribe(sub);

现在的问题是,当我得到肯定答案时,我需要终止发射(GameTurn)。我读到了takeUntil方法,我需要传递另一个Observable,它会发出一次会触发我的轮询终止的东西。但我不确定如何实现这一点。 根据您的解决方案,您的API方法会返回Observable,就像它在Retrofit网站上显示的那样。也许这是解决方案?那怎么会有用呢?

<强>更新 我考虑了@ david.miholas建议并尝试重试和过滤他的建议。您可以在下面找到游戏初始化的代码。轮询应该完全相同:Player1开始新游戏 - &gt;对于对手的民意调查,Player2加入游戏 - &gt;服务器发送给Player1对手的ID - &gt;投票终止。

    Subscriber sub =  new Subscriber<String>() {
        @Override
        public void onNext(String _SearchOpponentResult) {}

        @Override
        public void onCompleted() {
            Log.d("Polling", "Completed!");
        }

        @Override
        public void onError(Throwable e) {
            Log.d("Polling", "Error: " + e);
        }
    };

    Observable.interval(3, TimeUnit.SECONDS, Schedulers.io())
            .map(tick -> mApiService.SearchForOpponent(mGameInfo.GetGameID()))
            .doOnError(err -> Log.e("Polling", "Error retrieving messages: " + err))
            .retry()
            .filter(new Func1<String, Boolean>()
            {
                @Override
                public Boolean call(String _SearchOpponentResult)
                {
                    Boolean OpponentExists;
                    if (_SearchOpponentResult != "0")
                    {
                        Log.e("Polling", "Filter " + _SearchOpponentResult);
                        OpponentExists = true;
                    }
                    else
                    {
                        OpponentExists = false;
                    }
                    return OpponentExists;

                }
            })
            .take(1)
            .subscribe(sub);

发射是正确的,但是每次发射都会收到此日志消息:

E/Polling﹕ Error retrieving messages: java.lang.NullPointerException

在每次发射时都会触发doOnError。通常我会在每次发射时获得一些Retrofit调试日志,这意味着mApiService.SearchForOpponent不会被调用。我做错了什么?