【问题标题】:How should I handle "No internet connection" with Retrofit on Android我应该如何在 Android 上使用 Retrofit 处理“无互联网连接”
【发布时间】:2014-01-14 05:07:12
【问题描述】:

我想处理没有互联网连接的情况。通常我会跑:

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

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

(来自here)在将请求发送到网络之前并在没有互联网连接时通知用户。

据我所知,Retrofit 并没有专门处理这种情况。如果没有互联网连接,我将得到RetrofitError,并以超时为理由。

如果我想通过 Retrofit 将这种检查合并到每个 HTTP 请求中,我应该怎么做?或者我应该这样做。

谢谢

亚历克斯

【问题讨论】:

  • 您可以使用 try-catch 块来捕获 http 连接的超时异常。然后,告诉用户互联网连接状态。不漂亮,但另一种解决方案。
  • 是的,但是用 Android 检查它是否有互联网连接而不是等待从套接字获取连接超时要快得多
  • Android Query 处理“没有互联网”并很快返回相应的错误代码。也许值得用 aQuery 替换它?另一种解决方案是在网络变化时创建一个监听器,因此应用程序将在发送请求之前知道互联网可用性。
  • 对于改造 2,请参阅 github.com/square/retrofit/issues/1260

标签: android retrofit


【解决方案1】:

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

}

【讨论】:

  • Retrofit 源码中有一个提供的 ErrorHandler 可以使用。如果你不自己处理错误,Retrofit 会给你一个 RetrofitError [ java.net.UnknownHostException: Unable to resolve host "example.com": No address associated with hostname ]
  • @Codeversed 那么让isNetworkError 摆脱无法解决主机错误?
  • 你将如何在你的界面中实现它,客户端?我的意思是,你把这个类与什么联系起来?
  • cause.isNetworkError() 已弃用:使用error.getKind() == RetrofitError.Kind.NETWORK
【解决方案2】:

我最终做的是创建一个自定义 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(), ...))

【讨论】:

  • 您的 NetworkConnectivityManager 课程来自哪里?自定义?
  • 是的,自定义。基本上是问题中的代码
  • OkHttpClient 不起作用,而不是使用 OKClient().BDW 很好的答案。谢谢。
  • 谢谢:-)。正确使用 OkHttp 编辑了您的答案。
  • @MominAlAziz 取决于您如何定义NoConnectivityException,您可以使其扩展IOException 或使其扩展RuntimeException
【解决方案3】:

自从改造 1.8.0 后,这已被弃用

retrofitError.isNetworkError()

你必须使用

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

}

您可以处理多种类型的错误:

NETWORK 与服务器通信时发生 IOException,例如超时、无连接等...

CONVERSION(反)序列化正文时引发异常。

HTTP 从服务器接收到非 200 HTTP 状态代码,例如502、503等……

UNEXPECTED 尝试执行请求时发生内部错误。最佳做法是重新引发此异常,以便您的应用程序崩溃。

【讨论】:

  • 避免在变量上使用equals,始终使用CONSTANT.equals(variable) 来避免可能的NullPointerException。或者在这种情况下甚至更好,枚举接受 == 比较,所以 error.getKind() == RetrofitError.Kind.NETWORK 可能是更好的方法
  • 如果您厌倦了 NPE 和其他语法限制/痛苦,请用 Kotlin 替换 Java
【解决方案4】:

用于改造 1

当你从你的http请求中得到Throwable错误时,你可以用这样的方法检测是否是网络错误:

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

【讨论】:

    【解决方案5】:

    在 Retrofit 2 中,我们使用 OkHttp 拦截器实现在发送请求之前检查网络连接。如果没有网络,则酌情抛出异常。

    这允许人们在改造之前专门处理网络连接问题。

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

    【讨论】:

    • 如果您打开飞行模式然后将其关闭,这是有缺陷的,因为您只设置了一次 var,使 observable 无用。
    • 所以你正在制作一个OkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE)) HERE 应该是什么?
    • 请您包含该代码以便人们了解全貌吗?
    • @Kevin 如何确保在连接可用时更新?
    • 这应该是公认的答案。更多示例:migapro.com/detect-offline-error-in-retrofit-2
    【解决方案6】:

    您可以使用此代码

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

    【讨论】:

      【解决方案7】:

      只要这样做,即使是像

      这样的问题,您也会收到通知

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

      【讨论】:

        【解决方案8】:

        这是我在 API 29 &amp; API 30 上所做的:

        1.我创建了一个简单的 WiFiService 类来保存connectivityManager:

           class WifiService {
            private lateinit var wifiManager: WifiManager
            private lateinit var connectivityManager: ConnectivityManager
        
            companion object {
                val instance = WifiService()
            }
        
            fun initializeWithApplicationContext (context: Context) {
                wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
                connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            }
        
            // Helper that detects if online
            fun isOnline(): Boolean {
                val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
                if (capabilities != null) {
                    when {
                        capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> return true
                        capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> return true
                        capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> return true
                    }
                }
                return false
              }
           }
        

        2。创建 ConnectivityInterceptor 以检查互联网访问:

           class ConnectivityInterceptor: Interceptor {
            override fun intercept(chain: Interceptor.Chain): Response {
                if (!WifiService.instance.isOnline()) {
                    throw IOException("No internet connection")
                } else {
                    return chain.proceed(chain.request())
                }
             }
           }
        

        3.在 Retrofit2 中使用如下:

          class RestApi {
            private val okHttpClient by lazy {
                OkHttpClient.Builder()
                    .addInterceptor(ConnectivityInterceptor())
                    .build()
            }
        
            // Define all the retrofit clients
            private val restApiClient by lazy {
                Retrofit.Builder()
                    .baseUrl("http://localhost:10000")
                    .client(okHttpClient)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
             }
        
             // ...
          }
        

        4.最后像这样初始化WifiService:

        class MainApplication: Application() {
          companion object {
            lateinit var instance:  MainApplication
          }
        
          override fun onCreate() {
            super.onCreate()
            instance = this
        
            setupServices()
          }
        
          private fun setupServices() {
            WifiService.instance.initializeWithApplicationContext(this)
          }
        }
        

        【讨论】:

        • 如果有的话,你在哪里打电话给initializeWithApplicationContext(context)
        • @SudhirKhanger 来自我的应用程序的class MainApplication: Application()
        • 你是个好人。非常感谢
        猜你喜欢
        • 2016-11-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多