Jak poradzić sobie z „Brak połączenia z Internetem” z Retrofit na Androidzie

119

Chciałbym poradzić sobie z sytuacjami, gdy nie ma połączenia z Internetem. Zwykle biegałem:

ConnectivityManager cm =
    (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);

NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null &&
                  activeNetwork.isConnectedOrConnecting();

( stąd ) przed wysłaniem żądań do sieci i powiadom użytkownika, jeśli nie ma połączenia z Internetem.

Z tego co widziałem Retrofit nie radzi sobie specjalnie w tej sytuacji. Jeśli nie ma połączenia z Internetem, RetrofitErrorjako powód podam limit czasu.

Jeśli chciałbym włączyć ten rodzaj sprawdzania do każdego żądania HTTP z funkcją Retrofit, jak mam to zrobić? A może w ogóle powinienem to zrobić.

Dzięki

Alex

AlexV
źródło
Możesz użyć bloku try-catch, aby złapać wyjątek limitu czasu dla połączenia HTTP. Następnie poinformuj użytkowników o stanie połączenia internetowego. Nie ładne, ale alternatywne rozwiązanie.
Tugrul
8
Tak, ale znacznie szybciej jest sprawdzić w systemie Android, czy ma połączenie z Internetem, zamiast czekać na
przekroczenie
Aplikacja Android Query obsługuje „brak internetu” i wkrótce zwraca odpowiedni kod błędu. Może warto go zastąpić aQuery? Innym rozwiązaniem jest stworzenie nasłuchiwania zmian w sieci, dzięki czemu aplikacja będzie wiedziała o dostępności internetu przed wysłaniem zapytania.
Stan
dla modernizacji 2, patrz github.com/square/retrofit/issues/1260
ghanbari

Odpowiedzi:

63

Skończyło się na utworzeniu niestandardowego klienta Retrofit, który sprawdza łączność przed wykonaniem żądania i zgłasza wyjątek.

public class ConnectivityAwareUrlClient implements Client {

    Logger log = LoggerFactory.getLogger(ConnectivityAwareUrlClient.class);

    public ConnectivityAwareUrlClient(Client wrappedClient, NetworkConnectivityManager ncm) {
        this.wrappedClient = wrappedClient;
        this.ncm = ncm;
    }

    Client wrappedClient;
    private NetworkConnectivityManager ncm;

    @Override
    public Response execute(Request request) throws IOException {
        if (!ncm.isConnected()) {
            log.debug("No connectivity %s ", request);
            throw new NoConnectivityException("No connectivity");
        }
        return wrappedClient.execute(request);
    }
}

a następnie użyj go podczas konfigurowania RestAdapter

RestAdapter.Builder().setEndpoint(serverHost)
                     .setClient(new ConnectivityAwareUrlClient(new OkHttpClient(), ...))
AlexV
źródło
1
Skąd pochodzi Twoja klasa NetworkConnectivityManager? Zwyczaj?
NPike
Tak, niestandardowe. Zasadniczo jest to kod z pytania
AlexV,
2
OkHttpClient nie działa zamiast tego użyj OKClient () .BDW fajna odpowiedź. Dziękuję Ci.
Harshvardhan Trivedi
Dzięki :-). Zredagowałeś swoją odpowiedź przy odpowiednim użyciu OkHttp.
user1007522
1
@MominAlAziz, w zależności od tego, jak zdefiniujesz NoConnectivityException, możesz go rozszerzyć IOExceptionlub rozszerzyćRuntimeException
AlexV
45

Od czasu modernizacji 1.8.0jest to przestarzałe

retrofitError.isNetworkError()

musisz użyć

if (retrofitError.getKind() == RetrofitError.Kind.NETWORK)
{

}

istnieje wiele typów błędów, z którymi możesz sobie poradzić:

NETWORK Wystąpił wyjątek IOException podczas komunikacji z serwerem, np. Przekroczenie limitu czasu, brak połączenia itp.

CONVERSION Zgłoszono wyjątek podczas (de) serializacji treści.

HTTP Z serwera odebrano kod stanu HTTP inny niż 200, np. 502, 503 itd.

UNEXPECTEDWystąpił błąd wewnętrzny podczas próby wykonania żądania. Najlepszym rozwiązaniem jest ponowne zgłoszenie tego wyjątku, aby aplikacja uległa awarii.

Muhammad Alfaifi
źródło
8
Unikaj używania equalsnad zmiennymi, zawsze używaj CONSTANT.equals(variable)zamiast tego, aby uniknąć możliwego NullPointerException. Lub jeszcze lepiej w tym przypadku, wyliczenia akceptują == porównanie, więc error.getKind() == RetrofitError.Kind.NETWORKmoże być lepszym podejściem
MariusBudin
1
Zamień Javę na Kotlin, jeśli masz dość NPE i innych ograniczeń / bólu składni
Louis CAD,
42

W przypadku Retrofit 2 używamy implementacji OkHttp Interceptor do sprawdzania łączności sieciowej przed wysłaniem żądania. Jeśli nie ma sieci, odpowiednio wyślij wyjątek.

Pozwala to konkretnie poradzić sobie z problemami z łącznością sieciową przed przejściem do Retrofit.

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.Response;
import io.reactivex.Observable

public class ConnectivityInterceptor implements Interceptor {

    private boolean isNetworkActive;

    public ConnectivityInterceptor(Observable<Boolean> isNetworkActive) {
       isNetworkActive.subscribe(
               _isNetworkActive -> this.isNetworkActive = _isNetworkActive,
               _error -> Log.e("NetworkActive error " + _error.getMessage()));
    }

    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        if (!isNetworkActive) {
            throw new NoConnectivityException();
        }
        else {
            Response response = chain.proceed(chain.request());
            return response;
        }
    }
}

public class NoConnectivityException extends IOException {

    @Override
    public String getMessage() {
        return "No network available, please check your WiFi or Data connection";
    }
}
Kevin
źródło
3
Jest to wadliwe, jeśli włączysz tryb samolotowy, a następnie go wyłącz, ponieważ ustawiasz zmienną tylko wtedy, gdy obserwowalne stają się bezużyteczne.
Oliver Dixon
3
Więc robisz co OkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE))powinno być TUTAJ?
Rumid
3
Czy możesz dołączyć ten kod, aby ludzie mieli pełny obraz?
Oliver Dixon
1
@Kevin Jak mogę się upewnić, że zostanie zaktualizowany, gdy połączenie będzie dostępne?
Rumid
3
powinna to być akceptowana odpowiedź. więcej przykładów tutaj: migapro.com/detect-offline-error-in-retrofit-2
j2emanue
35

@AlexV Czy jesteś pewien, że błąd RetrofitError zawiera limit czasu jako przyczynę (wyjątek SocketTimeOutException, gdy wywoływana jest metoda getCause ()), gdy nie ma połączenia z Internetem?

O ile mi wiadomo, gdy nie ma połączenia z Internetem, RetrofitError zawiera jako przyczynę wyjątek ConnectionException.

Jeśli zaimplementujesz ErrorHandler , możesz zrobić coś takiego:

public class RetrofitErrorHandler implements ErrorHandler {

    @Override
    public Throwable handleError(RetrofitError cause) {
        if (cause.isNetworkError()) {
            if (cause.getCause() instanceof SocketTimeoutException) {
                return new MyConnectionTimeoutException();
            } else {
                return new MyNoConnectionException();
            }
        } else {
            [... do whatever you want if it's not a network error ...]  
        }
    }

}
saguinav
źródło
1
W źródle retrofitu znajduje się podany ErrorHandler, którego możesz użyć. Jeśli sam nie poradzisz sobie z tym błędem, Retrofit wyświetli błąd RetrofitError o wartości [java.net.UnknownHostException: Nie można rozpoznać hosta „example.com”: Brak adresu skojarzonego z nazwą hosta]
Codeversed
1
@Codeversed, więc czy isNetworkErrorpozbywanie się błędu hosta jest niemożliwe?
Wszechobecny
2
jak zaimplementowałbyś to w swoim interfejsie, kliencie? To znaczy, z czym łączysz tę klasę?
FRR
23
przyczyna.isNetworkError () jest przestarzała : użyjerror.getKind() == RetrofitError.Kind.NETWORK
Hugo Gresse
6

Do modernizacji 1

Gdy otrzymujesz Throwablebłąd z żądania http, możesz wykryć, czy jest to błąd sieci, za pomocą takiej metody:

String getErrorMessage(Throwable e) {
    RetrofitError retrofitError;
    if (e instanceof RetrofitError) {
        retrofitError = ((RetrofitError) e);
        if (retrofitError.getKind() == RetrofitError.Kind.NETWORK) {
            return "Network is down!";
        }
    }
}
IgorGanapolsky
źródło
5

po prostu zrób to, otrzymasz powiadomienie nawet w przypadku problemów takich jak

UnknownHostException

,

SocketTimeoutException

i inni.

 @Override public void onFailure(Call<List<BrokenGitHubRepo>> call, Throwable t) {  
if (t instanceof IOException) {
    Toast.makeText(ErrorHandlingActivity.this, "this is an actual network failure :( inform the user and possibly retry", Toast.LENGTH_SHORT).show();
    // logging probably not necessary
}
else {
    Toast.makeText(ErrorHandlingActivity.this, "conversion issue! big problems :(", Toast.LENGTH_SHORT).show();
    // todo log to some central bug tracking service
} }
Deepak sharma
źródło
2

możesz użyć tego kodu

Response.java

import com.google.gson.annotations.SerializedName;

/**
 * Created by hackro on 19/01/17.
 */

public class Response {
    @SerializedName("status")
    public String status;

    public void setStatus(String status) {
        this.status = status;
    }

    public String getStatus() {
        return status;
    }

    @SuppressWarnings({"unused", "used by Retrofit"})
    public Response() {
    }

    public Response(String status) {
        this.status = status;
    }
}

NetworkError.java

import android.text.TextUtils;

import com.google.gson.Gson;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import retrofit2.adapter.rxjava.HttpException;

import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;

/**
 * Created by hackro on 19/01/17.
 */

public class NetworkError extends Throwable {
    public static final String DEFAULT_ERROR_MESSAGE = "Please try again.";
    public static final String NETWORK_ERROR_MESSAGE = "No Internet Connection!";
    private static final String ERROR_MESSAGE_HEADER = "Error Message";
    private final Throwable error;

    public NetworkError(Throwable e) {
        super(e);
        this.error = e;
    }

    public String getMessage() {
        return error.getMessage();
    }

    public boolean isAuthFailure() {
        return error instanceof HttpException &&
                ((HttpException) error).code() == HTTP_UNAUTHORIZED;
    }

    public boolean isResponseNull() {
        return error instanceof HttpException && ((HttpException) error).response() == null;
    }

    public String getAppErrorMessage() {
        if (this.error instanceof IOException) return NETWORK_ERROR_MESSAGE;
        if (!(this.error instanceof HttpException)) return DEFAULT_ERROR_MESSAGE;
        retrofit2.Response<?> response = ((HttpException) this.error).response();
        if (response != null) {
            String status = getJsonStringFromResponse(response);
            if (!TextUtils.isEmpty(status)) return status;

            Map<String, List<String>> headers = response.headers().toMultimap();
            if (headers.containsKey(ERROR_MESSAGE_HEADER))
                return headers.get(ERROR_MESSAGE_HEADER).get(0);
        }

        return DEFAULT_ERROR_MESSAGE;
    }

    protected String getJsonStringFromResponse(final retrofit2.Response<?> response) {
        try {
            String jsonString = response.errorBody().string();
            Response errorResponse = new Gson().fromJson(jsonString, Response.class);
            return errorResponse.status;
        } catch (Exception e) {
            return null;
        }
    }

    public Throwable getError() {
        return error;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        NetworkError that = (NetworkError) o;

        return error != null ? error.equals(that.error) : that.error == null;

    }

    @Override
    public int hashCode() {
        return error != null ? error.hashCode() : 0;
    }
}

Implementacja w Twoich metodach

        @Override
        public void onCompleted() {
            super.onCompleted();
        }

        @Override
        public void onError(Throwable e) {
            super.onError(e);
            networkError.setError(e);
            Log.e("Error:",networkError.getAppErrorMessage());
        }

        @Override
        public void onNext(Object obj) {   super.onNext(obj);        
    }
David Hackro
źródło