Come devo gestire "Nessuna connessione Internet" con Retrofit su Android


119

Mi piacerebbe gestire le situazioni in cui non c'è connessione a Internet. Di solito correrei:

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

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

(da qui ) prima di inviare le richieste alla rete e avvisare l'utente se mancava la connessione ad internet.

Da quello che ho visto Retrofit non gestisce questa situazione in modo specifico. Se non c'è una connessione a Internet, avrò solo il RetrofitErrortimeout come motivo.

Se desidero incorporare questo tipo di controllo in ogni richiesta HTTP con Retrofit, come dovrei farlo? O dovrei farlo affatto.

Grazie

alex


È possibile utilizzare il blocco try-catch per rilevare l'eccezione di timeout per la connessione http. Quindi, informa gli utenti sullo stato della connessione Internet. Non carino ma una soluzione alternativa.
Tugrul

8
Sì, ma è molto più veloce controllare con Android se dispone di una connessione Internet invece di attendere il timeout della connessione dalla presa
AlexV

Android Query gestisce la "mancanza di Internet" e restituisce il codice di errore corrispondente abbastanza presto. Forse vale la pena sostituirlo con aQuery? Un'altra soluzione è creare un listener sui cambiamenti di rete e quindi l'app conoscerà la disponibilità di Internet prima di inviare la richiesta.
Stan

Risposte:


63

Quello che ho finito per fare è creare un client Retrofit personalizzato che verifica la connettività prima di eseguire una richiesta e genera un'eccezione.

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

e quindi usalo durante la configurazione RestAdapter

RestAdapter.Builder().setEndpoint(serverHost)
                     .setClient(new ConnectivityAwareUrlClient(new OkHttpClient(), ...))

1
Da dove viene la tua classe NetworkConnectivityManager? Personalizzato?
NPike

Sì, personalizzato. È fondamentalmente il codice della domanda
AlexV

2
OkHttpClient non funziona invece di usare OKClient (). Bella risposta BDW. grazie.
Harshvardhan Trivedi

Grazie :-). Modificato la tua risposta con il giusto uso di OkHttp.
user1007522

1
@MominAlAziz a seconda di come definisci NoConnectivityException, puoi estenderlo IOExceptiono farlo estendereRuntimeException
AlexV

45

Dal retrofit 1.8.0questo è stato deprecato

retrofitError.isNetworkError()

devi usare

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

}

ci sono più tipi di errori che puoi gestire:

NETWORK Si è verificata una IOException durante la comunicazione con il server, ad esempio Timeout, Nessuna connessione, ecc ...

CONVERSION È stata generata un'eccezione durante la (de) serializzazione di un corpo.

HTTP Un codice di stato HTTP non 200 è stato ricevuto dal server, ad esempio 502, 503, ecc ...

UNEXPECTEDSi è verificato un errore interno durante il tentativo di eseguire una richiesta. È consigliabile lanciare nuovamente questa eccezione in modo che l'applicazione si arresti in modo anomalo.


8
Evita di usare equalspiù variabili, usa sempre CONSTANT.equals(variable)invece per evitare possibili NullPointerException. O ancora meglio in questo caso, le enumerazioni accettano il confronto ==, quindi error.getKind() == RetrofitError.Kind.NETWORKpotrebbe essere un approccio migliore
MariusBudin

1
Sostituisci Java con Kotlin se sei stanco di NPE e altre limitazioni / problemi di sintassi
Louis CAD

42

Con Retrofit 2, utilizziamo un'implementazione di OkHttp Interceptor per verificare la connettività di rete prima di inviare la richiesta. Se nessuna rete, lancia un'eccezione come appropriato.

Ciò consente di gestire in modo specifico i problemi di connettività di rete prima di eseguire il 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";
    }
}

3
È difettoso se attivi la modalità aereo e poi la disattivi perché imposti la var solo una volta rendendo inutile l'osservabile.
Oliver Dixon

3
Quindi stai realizzando un OkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE))Cosa dovrebbe esserci QUI?
Rumid

3
Per favore, puoi includere quel codice in modo che le persone abbiano un'immagine completa?
Oliver Dixon

1
@Kevin come posso assicurarmi che si aggiornerà una volta che la connessione sarà disponibile?
Rumid

3
questa dovrebbe essere la risposta accettata. altro esempio qui: migapro.com/detect-offline-error-in-retrofit-2
j2emanue

35

@AlexV sei sicuro che RetrofitError contenga un timeout come motivo (SocketTimeOutException quando viene chiamato getCause ()) quando non c'è connessione a Internet?

Per quanto ne so quando non c'è connessione a Internet, RetrofitError contiene una ConnectionException come causa.

Se implementi un ErrorHandler puoi fare qualcosa di simile:

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

}

1
C'è un ErrorHandler fornito nella sorgente Retrofit che puoi usare. Se non gestisci l'errore da solo, Retrofit ti fornirà un RetrofitError di [java.net.UnknownHostException: Impossibile risolvere l'host "example.com": nessun indirizzo associato al nome host]
Codeversed

1
@ Codeversed quindi non è isNetworkErrorpossibile eliminare l'errore dell'host?
Onnipresente

2
come implementeresti questo nella tua interfaccia, cliente? Voglio dire, a cosa colleghi questa classe?
FRR

23
cause.isNetworkError () è deprecato : useerror.getKind() == RetrofitError.Kind.NETWORK
Hugo Gresse

6

Per retrofit 1

Quando ricevi un Throwableerrore dalla tua richiesta http, puoi rilevare se si tratta di un errore di rete con un metodo come questo:

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

5

basta farlo ti verrà notificato anche per problemi come

UnknownHostException

,

SocketTimeoutException

e altri.

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

2

puoi usare questo codice

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

Implementazione nei tuoi metodi

        @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);        
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.