Wie soll ich mit "Keine Internetverbindung" mit Retrofit auf Android umgehen?

119

Ich möchte Situationen behandeln, in denen keine Internetverbindung besteht. Normalerweise würde ich laufen:

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

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

(von hier aus ) bevor Sie die Anforderungen an das Netzwerk senden und den Benutzer benachrichtigen, wenn keine Internetverbindung besteht.

Nach allem, was ich gesehen habe, geht Retrofit nicht speziell mit dieser Situation um. Wenn es keine Internetverbindung gibt, bekomme ich nur eine RetrofitErrorZeitüberschreitung als Grund.

Wie soll ich vorgehen, wenn ich diese Art der Prüfung in jede HTTP-Anfrage mit Retrofit integrieren möchte? Oder sollte ich es überhaupt tun?

Vielen Dank

Alex

AlexV
quelle
Sie können den Try-Catch-Block verwenden, um eine Timeout-Ausnahme für die http-Verbindung abzufangen. Informieren Sie dann die Benutzer über den Status der Internetverbindung. Nicht schön, aber eine alternative Lösung.
Tugrul
8
Ja, aber es ist viel schneller mit Android zu überprüfen, ob es eine Internetverbindung hat, anstatt darauf zu warten, dass das Verbindungszeitlimit vom Socket kommt
AlexV
Android Query behandelt das "kein Internet" und gibt den entsprechenden Fehlercode früh genug zurück. Vielleicht lohnt es sich, es durch aQuery zu ersetzen? Eine andere Lösung besteht darin, einen Listener für Netzwerkänderungen zu erstellen, damit die App vor dem Senden der Anfrage über die Internetverfügbarkeit informiert wird.
Stan
Nachrüstung 2 finden Sie unter github.com/square/retrofit/issues/1260
ghanbari

Antworten:

62

Am Ende habe ich einen benutzerdefinierten Retrofit-Client erstellt, der vor dem Ausführen einer Anforderung auf Konnektivität überprüft und eine Ausnahme auslöst.

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

und verwenden Sie es dann bei der Konfiguration RestAdapter

RestAdapter.Builder().setEndpoint(serverHost)
                     .setClient(new ConnectivityAwareUrlClient(new OkHttpClient(), ...))
AlexV
quelle
1
Woher stammt Ihre NetworkConnectivityManager-Klasse? Benutzerdefiniert?
NPike
Ja, benutzerdefiniert. Es ist im Grunde der Code aus der Frage
AlexV
2
OkHttpClient funktioniert stattdessen nicht OKClient (). BDW nette Antwort. Danke.
Harshvardhan Trivedi
Vielen Dank :-). Bearbeiten Sie Ihre Antwort mit der richtigen Verwendung von OkHttp.
Benutzer1007522
1
@MominAlAziz je nachdem, wie Sie definieren NoConnectivityException, können Sie es entweder erweitern IOExceptionoder erweitern lassenRuntimeException
AlexV
45

Seit der Nachrüstung 1.8.0ist dies veraltet

retrofitError.isNetworkError()

du musst benutzen

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

}

Es gibt mehrere Arten von Fehlern, die Sie behandeln können:

NETWORK Während der Kommunikation mit dem Server ist eine IOException aufgetreten, z. B. Timeout, Keine Verbindung usw.

CONVERSION Beim (De-) Serialisieren eines Körpers wurde eine Ausnahme ausgelöst.

HTTP Ein Nicht-200-HTTP-Statuscode wurde vom Server empfangen, z. B. 502, 503 usw.

UNEXPECTEDBeim Versuch, eine Anforderung auszuführen, ist ein interner Fehler aufgetreten. Es wird empfohlen, diese Ausnahme erneut auszulösen, damit Ihre Anwendung abstürzt.

Muhammad Alfaifi
quelle
8
Vermeiden Sie die Verwendung von Übervariablen. equalsVerwenden Sie CONSTANT.equals(variable)stattdessen immer stattdessen, um mögliche NullPointerException zu vermeiden. Oder noch besser in diesem Fall, Aufzählungen akzeptieren == Vergleich, also error.getKind() == RetrofitError.Kind.NETWORKkönnte ein besserer Ansatz sein
MariusBudin
1
Ersetzen Sie Java durch Kotlin, wenn Sie NPEs und andere Syntaxbeschränkungen / Schmerzen satt haben
Louis CAD
42

Bei Retrofit 2 verwenden wir eine OkHttp Interceptor-Implementierung, um vor dem Senden der Anforderung die Netzwerkkonnektivität zu überprüfen. Wenn kein Netzwerk vorhanden ist, lösen Sie eine entsprechende Ausnahme aus.

Auf diese Weise können Probleme mit der Netzwerkkonnektivität speziell behandelt werden, bevor Retrofit aktiviert wird.

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
quelle
3
Es ist fehlerhaft, wenn Sie den Flugzeugmodus aktivieren und dann deaktivieren, da Sie die Variable nur einmal einstellen, um das Beobachtbare unbrauchbar zu machen.
Oliver Dixon
3
Also machst du ein OkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE))Was sollte HIER sein?
Rumid
3
Können Sie diesen Code bitte einfügen, damit die Leute ein ganzes Bild bekommen?
Oliver Dixon
1
@ Kevin Wie kann ich sicherstellen, dass es aktualisiert wird, sobald die Verbindung verfügbar ist?
Rumid
3
Dies sollte die akzeptierte Antwort sein. Weitere Beispiele hierfür: migapro.com/detect-offline-error-in-retrofit-2
j2emanue
35

@AlexV Sind Sie sicher, dass der RetrofitError eine Zeitüberschreitung als Grund enthält (SocketTimeOutException, wenn getCause () aufgerufen wird), wenn keine Internetverbindung besteht?

Soweit ich weiß, enthält der RetrofitError eine ConnectionException als Ursache, wenn keine Internetverbindung besteht.

Wenn Sie einen ErrorHandler implementieren, können Sie Folgendes tun:

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
quelle
1
In der Nachrüstquelle, die Sie verwenden können, befindet sich ein ErrorHandler. Wenn Sie den Fehler nicht selbst behandeln, erhalten Sie von Retrofit einen RetrofitError von [java.net.UnknownHostException: Host "example.com" kann nicht aufgelöst werden: Keine Adresse mit Hostname
verknüpft
1
@Codeversed Kann isNetworkErrorder Host-Fehler nicht behoben werden ?
Allgegenwärtig
2
Wie würden Sie dies in Ihre Schnittstelle implementieren, Client? Ich meine, womit verbindest du diese Klasse?
FRR
23
Ursache.isNetworkError () ist veraltet : verwendenerror.getKind() == RetrofitError.Kind.NETWORK
Hugo Gresse
6

Zur Nachrüstung 1

Wenn Sie Throwablevon Ihrer http-Anfrage einen Fehler erhalten, können Sie mit einer Methode wie der folgenden feststellen, ob es sich um einen Netzwerkfehler handelt:

String getErrorMessage(Throwable e) {
    RetrofitError retrofitError;
    if (e instanceof RetrofitError) {
        retrofitError = ((RetrofitError) e);
        if (retrofitError.getKind() == RetrofitError.Kind.NETWORK) {
            return "Network is down!";
        }
    }
}
IgorGanapolsky
quelle
5

Tun Sie dies einfach, Sie werden auch bei Problemen wie benachrichtigt

UnknownHostException

,

SocketTimeoutException

und andere.

 @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
quelle
1

Sie können diesen Code verwenden

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

Implementierung in Ihre Methoden

        @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
quelle