如何在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连接的超时异常。然后,向用户介绍Internet连接状态。不是很漂亮,而是一种替代解决方案。
Tugrul 2013年

8
是的,但是使用Android检查是否具有互联网连接而不是等待从套接字获取连接超时的速度要快得多
AlexV 2013年

Android Query处理“没有互联网”并尽快返回相应的错误代码。也许值得用aQuery替换它?另一个解决方案是在网络更改时创建一个侦听器,因此应用程序在发送请求之前将了解互联网的可用性。
Stan 2013年

Answers:


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 2014年

谢谢 :-)。正确使用OkHttp编辑了您的答案。
user1007522'2

1
@MominAlAziz取决于您的定义方式NoConnectivityException,您可以使其扩展IOException或使其扩展RuntimeException
AlexV 2015年

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
如果您厌倦了NPE和其他语法限制/痛苦,请用Kotlin替换Java
Louis CAD

42

对于Retrofit 2,我们使用OkHttp Interceptor实现在发送请求之前检查网络连接。如果没有网络,则根据情况抛出异常。

这样一来,在进行改造之前,就可以专门处理网络连接问题。

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))“这里应该有什么?”
拉米德(Rumid)'17

3
请您包含该代码,以便人们了解整个情况吗?
奥利弗·迪克森

1
@Kevin如何确定一旦连接可用就将更新?
拉米德(Rumid)'17年

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。如果你不自己处理错误,改造会给你的RetrofitError [的java.net.UnknownHostException:无法解析主机“example.com”:与主机名相关无地址]
Codeversed

1
@Codeversed这样isNetworkError可以摆脱无法解决的主机错误吗?
无所不在

2
客户端如何将其实现到您的界面中?我的意思是,您将这个课程与什么联系起来?
FRR 2014年

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);        
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.