2015-02-06 17 views
6

Buduję grę dla dwóch graczy na Androida. Gra działa odwrotnie, więc gracz 1 czeka, aż gracz 2 dokona swojego wejścia i na odwrót. Mam serwer WWW, na którym uruchamiam interfejs API w ramach Slim. Na klientach używam Retrofit. Tak więc na klientach chciałbym odpytać mój webserver (wiem, że to nie jest najlepsze podejście) co X sekund, aby sprawdzić, czy nie ma danych wejściowych od gracza 2, czy nie, jeśli tak, zmień UI (gameboard).Android: pobieranie z serwera za pomocą Retrofit

Radzenie sobie z modernizacją Natrafiłem na RxJavę. Moim problemem jest dowiedzieć się, czy muszę używać RxJava, czy nie? Jeśli tak, czy istnieją naprawdę proste przykłady odpytywania z modernizacją? (Ponieważ wysyłam tylko parę par klucz/wartość) A jeśli nie, jak to zrobić z modernizacją?

Znalazłem tutaj wątek this, ale to też mi nie pomogło, bo wciąż nie wiem, czy w ogóle potrzebuję Retrofitu + RxJava, czy może są łatwiejsze sposoby?

+0

Nie potrzebujesz * potrzebujesz * RxJava. Ale dobrze radzi sobie z modernizacją. – njzk2

+0

Jak mogę przeprowadzić pobieranie z Retrofitem bez RxJavy? – mrpool89

Odpowiedz

14

Powiedzmy interfejs zdefiniowano dla Retrofit zawiera metody takie jak to:

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

metody modernizacyjnej można określić w jeden z trzech sposobów:

1.) prosty synchronicznej One:

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

2.), który wziąć Callback dla asynchronicznego użytkowania:

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

3.) i ten, który zwraca rxjava Observable, patrz wyżej. Myślę, że jeśli zamierzasz używać Retrofit w połączeniu z rxjava, to ma sens używanie tej wersji.

ten sposób można po prostu użyć obserwowalne dla pojedynczego żądania bezpośrednio tak:

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 
}); 

Jeśli chcesz wielokrotnie odpytywać serwer przy użyciu można zapewnić „puls”, używając wersji timer() lub 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 
}). 

ważne jest, aby pamiętać, że używam flatMap tutaj zamiast map - to dlatego, że wartość zwracana loadGameState(mGameId) sama jest Observable.

Ale wersja używasz w swojej aktualizacji powinno działać zbyt:

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); 

Oznacza to, że jeśli ReceiveGameTurn() definiuje synchronicznie jak mój 1.) powyżej, należy użyć map zamiast flatMap.

W obu przypadkach onNext twojego Subscriber będzie wywoływane co dwie sekundy z najnowszym stanem gry z serwera. Możesz przetwarzać je jeden po drugim, ograniczając emisję do pojedynczego elementu, wstawiając take(1) przed subscribe().

Jednakże, w odniesieniu do pierwszej wersji: Jeden błąd sieci byłby pierwszy dostarczana do onError a następnie obserwowalnym zatrzyma emitując żadnych więcej przedmiotów, czyniąc abonent bezużyteczna i bez wejścia (pamiętaj, onError może być wywołana tylko raz). Aby obejść ten problem, można użyć dowolnej z metod rxjava, aby "przekierować" niepowodzenie na onNext.

Na przykład:

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 
}). 

Będzie co dwie sekundy: * Zastosowanie Retrofit aby uzyskać aktualny stan gry z serwera * odfiltrowanie nieprawidłowych ones * podejmują pierwsze ważne jedną * i wypisać

w przypadku błędu: * wypisze komunikat o błędzie w doOnNext * a inaczej zignorować błąd: onErrorResumeNext będzie „konsumować” z onError -Event (tj. twoje Subscriber 's onError nie będą wywoływane) i zastępuje je z niczym (Observable.empty()).

I, w odniesieniu do drugiego wersję: W przypadku błędu sieci retry natychmiast ponownie zarejestrować się w przedziale - a ponieważ interval emituje pierwszy Integer natychmiast po subskrypcji kolejna prośba zostanie wysłana natychmiast, za - i nie po 3 sekundach jak prawdopodobnie chcesz ...

Ostatnia uwaga: Jeśli twój stan gry jest dość duży, możesz najpierw odpytać serwer, aby zapytać, czy nowy stan jest dostępny i tylko w przypadku pozytywnej odpowiedzi przeładuj nowy stan gry.

Jeśli potrzebujesz bardziej wyszukanych przykładów, zapytaj.

AKTUALIZACJA: Przepisałem części tego wpisu i dodałem więcej informacji pomiędzy.

UPDATE 2: Dodałem pełny przykład obsługi błędów z onErrorResumeNext.

+0

Właśnie dodałem kolejny przykład obsługi błędów. –

+0

Będę musiał później przyjrzeć się obsłudze błędów. Ale czy nie używamy 'doOnError' do obsługi błędów? – mrpool89

+0

To zależy od tego, co masz na myśli mówiąc "obsługa". 'doOnError' po prostu _daje_ coś, gdy błąd jest przekazywany do obserwowalnej linii rur. Ale to nie jest naprawdę, jak sądzę, miejsce na rozwiązanie tego błędu. Domyślnie błędy mogą być obsługiwane w 'onError' - ale to również całkowicie wyłącza Observable. Istnieją inne opcje reagowania na błędy, które utrzymują subskrypcję w stanie nienaruszonym: emitują coś innego zamiast tego ('onErrorResumeNext'),' spróbuj ponownie ', itp. –

1

Dziękuję, w końcu zrobiłem to w podobny sposób bazując na post, o którym wspomniałem w moim pytaniu. Oto mój kod teraz:

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); 

Problem jest teraz, że muszę zakończyć emitujące kiedy dostanę pozytywną odpowiedź (a GameTurn). Przeczytałem o metodzie takeUntil, w której musiałbym przekazać kolejny Observable, który wyemitowałby coś, co mogłoby spowodować zakończenie mojego odpytywania. Ale nie jestem pewien, jak to wdrożyć. Zgodnie z rozwiązaniem, twoja metoda API zwraca wartość Observable, tak jak jest pokazana na stronie internetowej Retrofit. Może to jest rozwiązanie? Jak to by działało?

AKTUALIZACJA:Rozważałem porady @ david.miholas i wypróbowałem jego sugestię za pomocą ponownej próby i filtru. Poniżej znajduje się kod do inicjalizacji gry. Ankieta powinna działać identycznie: Gracz1 rozpoczyna nową grę -> ankiety dla przeciwnika, Gracz2 dołącza do gry -> serwer wysyła do gracza 1 ID przeciwnika -> odpytywanie zakończone.

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); 

Emisja jest poprawne, jednak otrzymuję komunikat dziennika na każdy emituje:

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

apperently doOnError jest uruchamiany na każdym emitują. Zazwyczaj dostaję niektóre dzienniki debugowania Retrofit na każdym wyemitowaniu, co oznacza, że ​​mApiService.SearchForOpponent nie zostanie wywołany. Co mam zrobić źle?

+0

Może mógłbyś wyjaśnić bardziej szczegółowo, co dokładnie chcesz osiągnąć: Na początku myślałem chciałem odpytywać serwer co dwie sekundy przez cały czas działania twojego programu, ale teraz mówisz, że chcesz zakończyć emisję przedmiotów po uzyskaniu pomyślnej odpowiedzi od serwera ... Można oczywiście po prostu wstawić ' weź (1) 'przed' subskrybuj', aby ograniczyć emisję do jednego elementu, ale możesz też rzucić okiem 'retryKiedy' - może to jest bliższe tego, czego naprawdę chcesz ... –

+0

To turowa gra , więc gracz czeka/odpytuje serwer w poszukiwaniu zmian. Kiedy inny gracz ustawia dane wejściowe, wysyła swoją turę, która jest zapisana w bazie danych. Pierwszy gracz odkrywa pozytywny wynik MySQL z informacjami o skręcie, które są potrzebne do aktualizacji interfejsu i zakończenia głosowania, ponieważ otrzymano informacje. Oficjalna wiki Rx mówi: _ "jeśli źródło Observable emituje błąd, przekaż ten błąd innemu Obserwatorowi, aby ustalić, czy chcesz ponownie wysłać subskrypcję do źródła" _, które nie pomaga mi – mrpool89

+0

A wersja, którą wysłałeś, zrobiła to, co chcesz, z wyjątkiem że nie zatrzymał się po jednym emitowanym przedmiocie? W takim przypadku możesz po prostu wstawić 'take (1)' pomiędzy 'retry()' i 'subscribe()' - w ten sposób 'take' będzie tylko" przepuścić "jeden element, a następnie anulować subskrypcję, a tym samym zatrzymać generowanie tyka w "przedziale". Jednak myślę, że w przypadku np. błąd sieciowy 'spróbuj ponownie 'od razu odnawia się do' interwału' - i ponieważ 'interval' emituje pierwszą liczbę całkowitą natychmiast po subskrypcji, następne żądanie zostanie wysłane również - i nie po 3 sekundach, jak prawdopodobnie chcesz ... –

Powiązane problemy