Як мені поводитися з "Немає з'єднання з Інтернетом" за допомогою Retrofit на Android


119

Я хотів би впоратися з ситуаціями, коли немає Інтернету. Зазвичай я бігав:

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

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

( звідси ), перш ніж надсилати запити в мережу та повідомляти користувача про відсутність підключення до Інтернету.

З того, що я бачив, Retrofit не вирішує цю ситуацію конкретно. Якщо немає підключення до Інтернету, я просто отримаю RetrofitErrorтайм-аут.

Якщо я хотів би включити подібний чек у кожен запит HTTP за допомогою Retrofit, як мені це зробити? Або я повинен це взагалі робити.

Дякую

Олексій


Ви можете використовувати блок try-catch для вилучення виключення тайм-ауту для з'єднання http. Потім повідомте користувачам про стан підключення до Інтернету. Не дуже, але альтернативне рішення.
Тугрул

8
Так, але набагато швидше перевірити Android, чи є у нього підключення до Інтернету, а не чекати, щоб отримати тайм-аут з'єднання з сокета
AlexV

Android Query обробляє "немає інтернету" і досить скоро повертає відповідний код помилки. Може бути, варто замінити його на aQuery? Ще одне рішення - створити слухача про зміни в мережі, і таким чином додаток дізнається про доступ до Інтернету перед відправкою запиту.
Стен

Відповіді:


63

Що я в кінцевому підсумку робив - це створити користувацький клієнт Retrofit, який перевіряє зв’язок перед виконанням запиту та видаляє виняток.

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

а потім використовувати його під час налаштування RestAdapter

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

1
Звідки ваш клас NetworkConnectivityManager? На замовлення?
NPike

Так, звичай. Це в основному код із запитання
AlexV

2
OkHttpClient не працює замість цього використовуйте OKClient (). BDW приємна відповідь. спасибі.
Harshvardhan Trivedi

Дякую :-). Відредагував свою відповідь правильним використанням OkHttp.
користувач1007522

1
@MominAlAziz залежно від того, як ви визначаєте NoConnectivityException, ви можете або зробити його розширеним, IOExceptionабо зробити його розширенимRuntimeException
AlexV,

45

З моменту модернізації 1.8.0ця система була застаріла

retrofitError.isNetworkError()

ви повинні використовувати

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

}

Є кілька типів помилок, з якими ви можете впоратися:

NETWORK IOException сталася під час спілкування з сервером, наприклад, час очікування, відсутність з'єднання тощо.

CONVERSION Виняток було кинуто під час (де) серіалізації тіла.

HTTP Код статусу HTTP не-200 був отриманий від сервера, наприклад 502, 503 і т.д. ...

UNEXPECTEDПри спробі виконання запиту виникла внутрішня помилка. Найкраще застосовувати цей виняток повторно, щоб ваш додаток вийшов з ладу.


8
Уникайте використання equalsперемінних змін, завжди використовуйте, CONSTANT.equals(variable)щоб уникнути можливого NullPointerException. Або ще краще в цьому випадку, перераховує прийняти == порівняння, тому error.getKind() == RetrofitError.Kind.NETWORKможе бути кращим підходом
МаріусБудін

1
Замініть Java на Котлін, якщо вам набридло NPE та інші синтаксичні обмеження / біль
Louis CAD

42

За допомогою Retrofit 2 ми використовуємо реалізацію перехоплювача OkHttp, щоб перевірити наявність підключення до мережі перед відправленням запиту. Якщо немає мережі, киньте виключення, якщо це доречно.

Це дозволяє спеціально вирішувати проблеми з мережевим підключенням перед тим, як вдатися до 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
Це помилка, якщо увімкнути режим літака, а потім вимкнути його, оскільки ви встановлюєте лише один раз, роблячи спостережуване марним.
Олівер Діксон

3
Отже, ви робите те, OkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE))що повинно бути тут?
Румід

3
Чи можете ви включити цей код, щоб люди отримали цілу картину?
Олівер Діксон

1
@Kevin, як я можу переконатися, що оновлення оновиться, коли з'єднання стане доступним?
Румід

3
це має бути прийнятою відповіддю. більше приклад цього тут: migapro.com/detect-offline-error-in-retrofit-2
j2emanue

35

@AlexV Ви впевнені, що RetrofitError містить тактику очікування (SocketTimeOutException, коли викликається getCause ()), коли немає підключення до Інтернету?

Наскільки мені відомо, коли немає підключення до Інтернету, RetrofitError містить причину ConnectionException.

Якщо ви реалізуєте ErrorHandler, ви можете зробити щось подібне:

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
У джерелі модернізації ви можете використовувати ErrorHandler, який ви можете використовувати. Якщо ви самі не впораєтеся з помилкою, Retrofit надасть вам RetrofitError [java.net.UnknownHostException: Не вдається вирішити хост "example.com": Немає адреси, пов’язаної з ім'ям хоста]
Зашифровано

1
@Codeversed так чи isNetworkErrorпозбувся не в змозі вирішити помилку хоста?
Всюдисуща

2
як би ти це реалізував у свій інтерфейс, клієнт? Я маю на увазі, до чого ви пов’язуєте цей клас?
FRR

23
cause.isNetworkError () є застарілим : використанняerror.getKind() == RetrofitError.Kind.NETWORK
Hugo Гресс

6

Для модернізації 1

Коли ви отримуєте Throwableпомилку від свого http-запиту, ви можете виявити, чи є мережевою помилкою такий метод:

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

5

просто зробіть це вам сповістимо навіть про такі питання

UnknownHostException

,

SocketTimeoutException

і інші.

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

1

ви можете використовувати цей код

Відповідь.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;
    }
}

Впровадження у ваші методи

        @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);        
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.