Android에서 Retrofit으로 "인터넷 연결 없음"을 어떻게 처리해야합니까?


119

인터넷에 연결되지 않은 상황을 처리하고 싶습니다. 일반적으로 다음을 실행합니다.

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

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

( 여기에서 ) 네트워크에 요청을 보내기 전에 인터넷에 연결되지 않은 경우 사용자에게 알립니다.

내가 본 것에서 Retrofit은이 상황을 구체적으로 처리하지 않습니다. 인터넷에 연결되어 있지 않으면 RetrofitError시간 초과를 이유로 하겠습니다 .

Retrofit을 사용하여 모든 HTTP 요청에 이러한 종류의 검사를 통합하려면 어떻게해야합니까? 아니면 내가해야만합니다.

감사

알렉스


try-catch 블록을 사용하여 http 연결에 대한 시간 초과 예외를 포착 할 수 있습니다. 그런 다음 사용자에게 인터넷 연결 상태를 알립니다. 예쁘지는 않지만 대체 솔루션입니다.
Tugrul

8
예,하지만 소켓에서 연결 시간 초과를 기다리는 대신 인터넷에 연결되어 있는지 Android로 확인하는 것이 훨씬 더 빠릅니다
AlexV dec.

Android 쿼리는 '인터넷 없음'을 처리하고 곧 해당 오류 코드를 반환합니다. 아마도 그것을 aQuery로 대체 할 가치가 있습니까? 또 다른 해결책은 네트워크 변경에 대한 리스너를 만드는 것이므로 앱은 요청을 보내기 전에 인터넷 가용성에 대해 알게됩니다.
Stan

답변:


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 2014

예, 맞춤입니다. 기본적으로 질문의 코드입니다
AlexV 2014

2
OkHttpClient는 OKClient (). BDW 좋은 대답을 사용하는 대신 작동하지 않습니다. 감사합니다.
Harshvardhan Trivedi

감사 :-). OkHttp를 올바르게 사용하여 답변을 편집했습니다.
user1007522

1
@MominAlAziz는 사용자가 정의하는 방식에 따라 NoConnectivityException, 당신도 그것을 확장 할 수 있습니다 IOException하거나 확장 할RuntimeException
AlexV

45

개조 이후이 기능 1.8.0은 더 이상 사용되지 않습니다.

retrofitError.isNetworkError()

당신은 사용해야합니다

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

}

처리 할 수있는 여러 유형의 오류가 있습니다.

NETWORK 서버와 통신하는 동안 IOException이 발생했습니다 (예 : 시간 초과, 연결 없음 등).

CONVERSION 본문을 (역) 직렬화하는 동안 예외가 발생했습니다.

HTTP 200이 아닌 HTTP 상태 코드가 서버에서 수신되었습니다 (예 : 502, 503 등).

UNEXPECTED요청을 실행하는 동안 내부 오류가 발생했습니다. 애플리케이션이 충돌하도록이 예외를 다시 발생시키는 것이 가장 좋습니다.


8
사용하지 마십시오 equals사용 항상 변수를 통해 CONSTANT.equals(variable)가능한 경우 NullPointerException을 피하기 위해 대신. 아니면 더 나은이 경우에 열거 형은 == 비교를 허용하므로 error.getKind() == RetrofitError.Kind.NETWORK더 나은 접근 방식이 될 수 있습니다
MariusBudin

1
당신이 NPEs 및 기타 구문 제한 / 고통의 피곤하면 코 틀린 자바 교체
루이 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
비행기 모드를 켠 다음 끄면 var를 한 번만 설정하여 관찰 가능한 것을 쓸모 없게 만들기 때문에 결함이 있습니다.
Oliver Dixon

3
그래서 당신은 OkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE))What should be in HERE를 만들고 있습니다.
Rumid

3
사람들이 전체 그림을 볼 수 있도록 해당 코드를 포함 할 수 있습니까?
Oliver Dixon

1
@Kevin 연결을 사용할 수있게되면 업데이트되는지 어떻게 확인할 수 있습니까?
Rumid

3
이것은 받아 들여진 대답이어야합니다. 여기 더 예 : migapro.com/detect-offline-error-in-retrofit-2
j2emanue

35

@AlexV 인터넷에 연결되어 있지 않을 때 RetrofitError가 이유 (getCause ()가 호출 될 때 SocketTimeOutException)로 시간 초과를 포함한다고 확신합니까?

인터넷 연결이 없을 때 내가 아는 한 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
사용할 수있는 Retrofit 소스에는 제공된 ErrorHandler가 있습니다. 오류를 직접 처리하지 않으면 Retrofit은 RetrofitError of [java.net.UnknownHostException : Unable to resolve host "example.com": No address associated with hostname]
Codeversed

1
@Codeversed 그래서 isNetworkError호스트 오류를 ​​해결할 수 없음을 제거합니까?
Omnipresent 2014 년

2
당신의 인터페이스, 클라이언트에 이것을 어떻게 구현 하시겠습니까? 이 수업을 무엇에 연결합니까?
FRR

23
cause.isNetworkError ()는 더 이상 사용되지 않습니다 : 사용error.getKind() == RetrofitError.Kind.NETWORK
Hugo Gresse

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

이 코드를 사용할 수 있습니다

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

방법의 구현

        @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);        
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.