我正在 Android 上构建一个 2 人游戏。游戏按顺序进行,因此玩家 1 等待直到玩家 2 做出输入,反之亦然。我有一个网络服务器,我在其中使用 Slim 运行 API框架。在我使用 Retrofit 的客户端上。因此,在客户端上,我想每隔 X 秒轮询一次我的网络服务器(我知道这不是最好的方法),以检查是否有来自玩家 2 的输入,如果是,则更改 UI(游戏板)。
处理 Retrofit 我遇到了 RxJava。我的问题是弄清楚我是否需要使用 RxJava?如果是,是否有任何真正简单的改造轮询示例? (因为我只发送了几个键/值对)如果不是如何用改造来代替呢?
我找到了 this线程在这里,但它对我也没有帮助,因为我仍然不知道我是否需要 Retrofit + RxJava,是否有更简单的方法?
最佳答案
假设您为 Retrofit 定义的接口(interface)包含如下方法:
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
的那个,见上文。我认为,如果您打算将 Retrofit 与 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
。
在这两种情况下,您的Subscriber
的onNext
将每两秒调用一次,并使用来自服务器的最新游戏状态。您可以通过在 subscribe()
之前插入 take(1)
来一个接一个地处理它们,或者将发射限制为单个项目。
然而,关于第一个版本:单个网络错误将首先传递给 onError
然后 Observable 将停止发射任何更多的项目,使您的订阅者无用且没有输入(记住,onError
只能调用一次)。要解决此问题,您可以使用 rxjava 的任何 onError*
方法将失败“重定向”到 onNext。
例如:
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
}).
这将每两秒: * 使用 Retrofit 从服务器获取当前游戏状态 * 过滤掉无效的 * 取第一个有效的 * 和退订
如果出现错误:
* 它将在 doOnNext
中打印一条错误消息
* 否则忽略错误:onErrorResumeNext
将“消耗”onError
-Event(即您的订阅者
的onError
将不会被调用)并将其替换为任何内容(Observable.empty()
)。
并且,关于第二个版本:如果出现网络错误,retry
将立即重新订阅间隔 - 因为 interval
在订阅下一个时立即发出第一个 Integer请求也将立即发送 - 而不是您可能想要的 3 秒后......
最后说明:此外,如果您的游戏状态非常大,您也可以先轮询服务器以询问是否有新状态可用,只有在肯定回答的情况下才重新加载新游戏状态。
如果您需要更详细的示例,请询问。
更新:我重写了这篇博文的部分内容并在其间添加了更多信息。
更新 2:我添加了使用 onErrorResumeNext
进行错误处理的完整示例。
关于Android:使用 Retrofit 轮询服务器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28369689/