android 改型2:捕获连接超时异常

xmq68pz9  于 2022-12-25  发布在  Android
关注(0)|答案(8)|浏览(124)

我有以下设置:

final OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setReadTimeout(5, TimeUnit.SECONDS);
okHttpClient.setConnectTimeout(5, TimeUnit.SECONDS);

RestAdapter.Builder builder = new RestAdapter.Builder()
        .setEndpoint(ROOT)
        .setClient(new OkClient(okHttpClient))
        .setLogLevel(RestAdapter.LogLevel.FULL);

我正在尝试处理我的服务器停机并且用户收到连接超时异常的情况,这是我的日志记录:

java.net.SocketTimeoutException: failed to connect to /192.168.0.53 (port 3000) after 5000ms

完整日志记录:http://pastebin.com/gscCGb7x
有没有办法把它转到改造故障方法中,这样我就可以在那里处理它了?

o0lyfsai

o0lyfsai1#

**用于 Retrofit 2 **

在Web服务示例中定义监听程序:

public interface OnConnectionTimeoutListener {
    void onConnectionTimeout();
}

将拦截器添加到Web服务:

public WebServiceClient() {
    OkHttpClient client = new OkHttpClient();
    client.setConnectTimeout(10, TimeUnit.SECONDS);
    client.setReadTimeout(30, TimeUnit.SECONDS);
    client.interceptors().add(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            return onOnIntercept(chain);
        }
    });
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .build();
    webService = retrofit.create(WebService.class);
}

用try-catch块封装拦截代码,并在异常发生时通知侦听器:

private Response onOnIntercept(Chain chain) throws IOException {
    try {
        Response response = chain.proceed(chain.request());
        String content = UtilityMethods.convertResponseToString(response);
        Log.d(TAG, lastCalledMethodName + " - " + content);
        return response.newBuilder().body(ResponseBody.create(response.body().contentType(), content)).build();
    }
    catch (SocketTimeoutException exception) {
        exception.printStackTrace();
        if(listener != null)
            listener.onConnectionTimeout();
    }

    return chain.proceed(chain.request());
}
af7jpaap

af7jpaap2#

@Override
public void onFailure(Call call, Throwable t) {
    if(t instanceof SocketTimeoutException){
        message = "Socket Time out. Please try again.";
    }
}
6yjfywim

6yjfywim3#

如果有人带着Kotlin/Coroutines来到这里,面临同样的问题,请在您的协程作用域中添加一个错误处理程序:

CoroutineScope(Dispatchers.IO).launch(handler) {

而处理程序本身看起来像:

val handler = CoroutineExceptionHandler { _, exception ->
    Log.t("Network", "Caught $exception")
}
ulydmbyx

ulydmbyx4#

没有一个答案对我很有效,但它们把我引向了正确的方向。请看下面我在Kotlin是如何做到的。
您可以在ErrorInterceptor中抛出异常,然后在api调用函数中捕获它们:

class ErrorInterceptor : Interceptor {

    override fun intercept(chain: Chain): Response {

        val request = chain.request()

        try {
            val response = chain.proceed(request)
            val bodyString = response.body!!.string()

            return response.newBuilder()
                .body(bodyString.toResponseBody(response.body?.contentType()))
                .build()
        } catch (e: Exception) {
            when (e) {
                is SocketTimeoutException -> {
                    throw SocketTimeoutException()
                }

               // Add additional errors... //

            }
        }
    }

或者将异常与响应对象捆绑在一起;就像这样:

class ErrorInterceptor : Interceptor {

    override fun intercept(chain: Chain): Response {

        val request = chain.request()

        try {
            val response = chain.proceed(request)
            val bodyString = response.body!!.string()

            return response.newBuilder()
                .body(bodyString.toResponseBody(response.body?.contentType()))
                .build()
        } catch (e: Exception) {
            var msg = ""
            val interceptorCode: Int

            when (e) {
                is SocketTimeoutException -> {

                    msg = "Socket timeout error"
                    interceptorCode = 408

                }

               // Add additional errors... //

            }

             return Response.Builder()
                .request(request)
                .protocol(Protocol.HTTP_1_1)
                .code(interceptorCode)
                .message(msg)
                .body("{${e}}".toResponseBody(null)).build()
        }
    }
}

ErrorInterceptor添加到您的okHttpClient

okHttpClient.newBuilder()
                .addInterceptor(ErrorInterceptor())
                .connectTimeout(10, TimeUnit.SECONDS)
                 // ... //
                .build()

然后在存储库层中显示如下内容:

suspend fun makeAPIRequest(): Resource<ApiResponse> {

        return withContext(ioDispatcher) {

            var response: Response<ApiResponse>? = null

            try {
                response = getResponse()

                // Do additional ops on response here //

            } catch (e: Exception) {

                // Exceptions thrown in ErrorInterceptor will propagate here

            }
        }
    }
oxiaedzo

oxiaedzo5#

显然,异常确实被 Package 到RetrofitException中,以便您可以在失败方法中处理它。

fdbelqdn

fdbelqdn6#

这有点复杂,使用Retrofit你可以进行同步或异步的API调用。
如果你的端点返回void并且有回调,那么它是异步的;如果它返回了一些东西并且没有回调,那么它是同步的。
对于异步调用,在回调的onFailure(...)方法中会出现此异常。
对于同步调用,除非您将调用 Package 在try/catch中,否则您根本无法获得它。

try {
   // your synchronous call goes here  
} catch (RetrofitError error) {
   // handle errors
}
gzszwxb4

gzszwxb47#

我发布这个有两个原因:
1.我个人尝试过增加连接超时,但是,事实上,它并没有真正从根本上解决问题。此外,根据this post,用户不应该等待超过10秒。
1.在真实的应用程序中,我们宁愿尽最大努力以尽可能干净的方式实现解决方案。
这是我在Kotlin中提出的一个解决方案,它的灵感来自于@Olcay Ertaš提供的the answer,并结合了Google's recommended architecture用于Android应用程序。
1.创建TimeoutInterceptor接口:

interface TimeoutInterceptor : Interceptor

1.实现TimeoutInterceptor接口:

class TimeoutInterceptorImpl : TimeoutInterceptor {

     override fun intercept(chain: Interceptor.Chain): Response {
         if (isConnectionTimedOut(chain))
             throw SocketTimeoutException()
         return chain.proceed(chain.request())
     }

     private fun isConnectionTimedOut(chain: Interceptor.Chain): Boolean {
         try {
             val response = chain.proceed(chain.request())
             val content = response.toString()
             response.close()
             Log.d(tag, "isConnectionTimedOut() => $content")
         } catch (e: SocketTimeoutException) {
             return true
         }
         return false
     }
 }

1.在ApiService界面中,将TimeoutInterceptor添加到OkHttpClient构建器:

val okHttpClient = OkHttpClient.Builder()
         .addInterceptor(requestInterceptor)
         // Add timeout interceptor
         .addInterceptor(timeoutInterceptor)
         // Set a 5s custom connect timout
         .connectTimeout(5, TimeUnit.SECONDS)
         .build()

你可能已经注意到了,你可以设置一个自定义的连接超时,否则,根据documentation,它会被设置为10秒的默认值。
1.创建一个枚举类ConnectionState,它将提供一个枚举常量对象CONNECTION_TIMEOUT,该对象将进一步用于将适当的连接(或API调用)状态从EntityNetworkDataSource类传递到View类(如果您遵循Google的MVVM模式):

enum class ConnectionState {
     CONNECTED, NOT_CONNECTED, CONNECTION_TIMEOUT
 }

1.假设您的EntityNetworkDataSource接口如下所示:

interface EntityNetworkDataSource {
     val fetchedEntity: LiveData<Entity>

     // Wrap your ConnectionState object in LiveData in order to be able to observe it in the View
     val connectionState: LiveData<ConnectionState>

     // Fetch `Entity` object from the network
     suspend fun fetchEntity(id: Int)
 }

1.在EntityNetworkDataSource实现类中,您可以在fetchEntity(id: Int)实现中正确捕获SocketTimeoutException,如下所示:

class EntityNetworkDataSourceImpl(
         private val apiService: ApiService
 ) : EntityNetworkDataSource {

     private val _fetchedEntity = MutableLiveData<Entity>()

     override val fetchedEntity: LiveData<Entity>
         get() = _fetchedEntity

     // We want to keep our MutableLiveData private because they can be changed.
     // So we want to be able to update them only from the inside of this class
     private val _connectionState = MutableLiveData<ConnectionState>()

     override val connectionState: LiveData<ConnectionState>
         get() = _connectionState

     override suspend fun fetchEntity(id: Int) {
         try {
             val fetchedEntity = apiService
                     .getEntity(id)
                     .await()

             // Convey the updated connection state to the observer View
             _connectionState.postValue(ConnectionState.CONNECTED)

             _fetchedEntity.postValue(fetchedEntity)
         } catch (e: SocketTimeoutException) {
             Log.e(tag, "Connection timeout. ", e)
             // Catch the SocketTimeoutException and post the updated connection state to the observer View
             _connectionState.postValue(ConnectionState.CONNECTION_TIMEOUT)
         }
     }
 }

同样的原则也适用于您想要拦截和捕获的任何连接异常。

fcipmucu

fcipmucu8#

Kotlin

如果要在Kotlin中使用Retrofit,请执行以下步骤:
定义Retrofit界面:

interface GitHubApi {

    @GET("/users/{userName}/repos")
    fun repos(@Path("userName") userName: String): Call<List<Repo>>
}

实施您的服务:

class Api(...) {

    private val baseUrl = "https://api.github.com"
    private val api: GitHubApi

    private fun loggingInterceptor(...): HttpLoggingInterceptor {...}

    private fun okHttpBuilder(): OkHttpClient {...}

    init {...}

    fun repos(
        userName: String,
        onSuccess: (list: List<Repo>?) -> Unit,
        onFailure: (message: String?) -> Unit): Future<Unit> {
        return runAsync(api.repos(userName), onSuccess, onFailure)
    }

    private fun <T> runAsync(
        call: retrofit2.Call<T>,
        onSuccess: (T?) -> Unit,
        onFailure: (message: String?) -> Unit) : Future<Unit> {
        return doAsync {
            try {
                val response = call.execute()
                when {
                    response.isSuccessful -> response.body()?.let {
                        onSuccess(it)
                    }
                    else -> {
                        onFailure(response.raw().message())
                    }
                }
            } catch (e: IOException) {
                if (e is SocketTimeoutException) {
                    onFailure("Response time out!")
                } else {
                    onFailure(e.message)
                }
            }
        }
    }
}

在您需要的位置呼叫您的服务:

Api().repos("olcayertas",
    onSuccess = {
        Log.d("MainActivity", "Response:\n" + toJson(it))
    },
    onFailure = {
        Log.e("MainActivity", "Error: $it")
    })

您可以在runAsync函数中处理任何您想要的异常。
您可以获得完整的工作示例here

相关问题