问题描述
我正在Android上构建2 Player游戏.游戏是按顺序进行的,因此玩家1等待直到玩家2进行输入,反之亦然.我有一个网络服务器,其中使用 Slim 框架运行API.在客户端上,我使用翻新.因此,我想在客户端上每隔X秒轮询一次我的Web服务器(我知道这不是最好的方法),以检查是否有玩家2的输入,如果是,请更改UI(游戏板).
I'm building a 2 Player game on Android. The game works turnwise, so player 1 waits until player 2 made his input and vice versa. I have a webserver where I run an API with the Slim Framework. On the clients I use Retrofit. So on the clients I would like to poll my webserver (I know it's not the best approach) every X seconds to check whether there was an input from player 2 or not, if yes change UI (the gameboard).
应对改造我遇到了RxJava.我的问题是弄清楚是否需要使用RxJava?如果是,是否有任何非常简单的示例来进行改造轮询? (因为我只发送了一对键/值对),如果不是,那么该如何进行改型呢?
Dealing with Retrofit I came across RxJava. My problem is to figure out whether I need to use RxJava or not? If yes, are there any really simple examples for polling with retrofit? (Since I send only a couple of key/value pairs) And if not how to do it with retrofit instead?
我发现了这线程在这里,但是它对我也没有帮助,因为我仍然不知道我是否完全需要Retrofit + RxJava,有没有更简单的方法?
I found this thread here but it didn't help me too because I still don't know if I need Retrofit + RxJava at all, are there maybe easier ways?
推荐答案
假设您为Retrofit定义的接口包含这样的方法:
Let's say the interface you defined for Retrofit contains a method like this:
public Observable<GameState> loadGameState(@Query("id") String gameId);
可以通过以下三种方式之一定义翻新方法:
Retrofit methods can be defined in one of three ways:
1.)一个简单的同步对象:
1.) a simple synchronous one:
public GameState loadGameState(@Query("id") String gameId);
2.)将Callback
用于异步处理的
2.) one that take a Callback
for asynchronous handling:
public void loadGameState(@Query("id") String gameId, Callback<GameState> callback);
3.)和返回rxjava Observable
的代码,请参见上文.我认为,如果您打算将Retrofit与rxjava结合使用,则使用此版本最有意义.
3.) and the one that returns an rxjava Observable
, see above. I think if you are going to use Retrofit in conjunction with rxjava it makes the most sense to use this version.
这样,您可以像这样直接将Observable用于单个请求:
That way you could just use the Observable for a single request directly like this:
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()
版本提供脉冲":
If you want to repeatedly poll the server using you can provide the "pulse" using versions of timer()
or 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
.
It is important to note that I am using flatMap
here instead of map
- that's because the return value of loadGameState(mGameId)
is itself an Observable
.
但是您在更新中使用的版本也应该工作:
But the version you are using in your update should work too:
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);
也就是说,如果像上面的1.)一样同步定义ReceiveGameTurn()
,则可以使用map
而不是flatMap
.
That is, if ReceiveGameTurn()
is defined synchronously like my 1.) above, you would use map
instead of flatMap
.
在这两种情况下,Subscriber
的onNext
都将每两秒钟被调用一次,并带有服务器的最新游戏状态.您可以一个接一个地处理它们,方法是在subscribe()
之前插入take(1)
,将发射限制为单个项目.
In both cases the onNext
of your Subscriber
would be called every two seconds with the latest game state from the server. You can process them one after another of limit the emission to a single item by inserting take(1)
before subscribe()
.
但是,对于第一个版本:单个网络错误将首先传递给onError
,然后Observable将停止发出更多项目,从而使订阅服务器变得无用且无输入(请记住,只能调用onError
一次).要解决此问题,您可以使用rxjava的任何onError*
方法将失败重定向"到onNext.
However, regarding the first version: A single network error would be first delivered to onError
and then the Observable would stop emitting any more items, rendering your Subscriber useless and without input (remember, onError
can only be called once). To work around this you could use any of the onError*
methods of rxjava to "redirect" the failure to 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
}).
这将每两秒钟:*使用翻新从服务器获取当前游戏状态*过滤掉无效的*取第一个有效的*和退订
This will every two seconds:* use Retrofit to get the current game state from the server* filter out invalid ones* take the first valid one* and the unsubscribe
如果发生错误:*它将在doOnNext
中打印一条错误消息*并忽略该错误:onErrorResumeNext
将消耗" onError
-事件(即,不会调用您的Subscriber
的onError
)并将其替换为任何内容(Observable.empty()
).
In case of an error:* it will print an error message in doOnNext
* and otherwise ignore the error: onErrorResumeNext
will "consume" the onError
-Event (i.e. your Subscriber
's onError
will not be called) and replaces it with nothing (Observable.empty()
).
并且,关于第二个版本:如果出现网络错误,retry
将立即重新订阅该间隔-并且由于interval
在订阅后立即发出第一个Integer,因此也将立即发送下一个请求-而不会3秒后,您可能想要...
And, regarding the second version: In case of a network error retry
would resubscribe to the interval immediately - and since interval
emits the first Integer immediately upon subscription the next request would be sent immediately, too - and not after 3 seconds as you probably want...
最后的注意:同样,如果您的游戏状态很大,您还可以先轮询服务器以询问是否有新状态,并且只有在肯定回答的情况下才重新加载新游戏状态.
Final note: Also, if your game state is quite large, you could also first just poll the server to ask whether a new state is available and only in case of a positive answer reload the new game state.
如果您需要更多详细的示例,请询问.
If you need more elaborate examples, please ask.
更新:我已经重写了本文的一部分,并在两者之间添加了更多信息.
UPDATE: I've rewritten parts of this post and added more information in between.
更新2 :我添加了使用onErrorResumeNext
进行错误处理的完整示例.
UPDATE 2: I've added a full example of error handling with onErrorResumeNext
.
这篇关于Android:使用翻新轮询服务器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!