我在Android上构建了一个2人游戏。游戏以转向方式工作,因此玩家1等待玩家2输入,反之亦然。我有一个网络服务器,我使用Slim框架运行API。在客户端我使用Retrofit。所以在客户端我想每隔X秒轮询我的网络服务器(我知道它不是最好的方法)来检查是否有来自玩家2的输入,如果是,则更改UI(游戏板)。
处理Retrofit我遇到了RxJava。我的问题是弄清楚我是否需要使用RxJava?如果是,是否有任何非常简单的例子用于改造轮询? (因为我只发送了几个键/值对)如果不是如何用改造来做呢?
我在这里找到this帖子,但它也没有帮助我,因为我还不知道我是否需要Retrofit + RxJava,有没有更简单的方法?
答案 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
不会被调用。我做错了什么?