Retrofit和OkHttpClient,在失败方法中捕获连接超时

时间:2015-04-28 13:58:02

标签: android retrofit connection-timeout okhttp

我有以下设置:

(new Handler(Looper.getMainLooper()).post(new Runnable() {
    public void run() {
        nameDoc = (TextView) findViewById(R.id.name);
        iddoc = (TextView) findViewById(R.id.docId);
        icdoc = (TextView) findViewById(R.id.icdoctor);
        adddoc = (TextView) findViewById(R.id.addressdoc);
        notel = (TextView) findViewById(R.id.noteldoc);

        nameDoc.setText(docProf.getString("name"));
        iddoc.setText(docProf.getString("id"));
        icdoc.setText(docProf.getString("ic"));
        adddoc.setText(docProf.getString("address"));
        notel.setText(docProf.getString("noTel"));
    }
});

我正在尝试处理我的服务器关闭并且用户获得连接超时异常的情况,这是我的日志记录:

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

完整记录:http://pastebin.com/gscCGb7x

有没有办法将其转换为改装失败方法,以便我可以在那里处理它?<​​/ p>

提前致谢!

8 个答案:

答案 0 :(得分:41)

For Retrofit 2

在Web服务实例中定义一个侦听器:

public interface OnConnectionTimeoutListener {
    void onConnectionTimeout();
}

向您的网络服务添加拦截器:

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

<强>科特林

如果您想在Retrofit中使用Kotlin,请按以下步骤操作:

定义您的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

答案 1 :(得分:29)

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

答案 2 :(得分:3)

显然,异常会被包装到RetrofitException中,因此您可以在失败方法中处理它。

答案 3 :(得分:3)

它有点复杂。使用Retrofit,您可以进行同步或异步的API调用。

如果您的端点返回void并且有回调,则它是异步的。 如果它返回某些内容并且没有回调,则它是同步的。

对于异步调用,您可以在回调的onFailure(...)方法中获得此异常。

对于同步通话,你根本不会得到它,除非你用try / catch包裹你的电话。

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

更新:上述答案适用于Retrofit 1.9。 Retrofit 2.0已经改变了很多。如果您想知道Retrofit 2.0中现在的工作方式,本文提供了一些指示http://inthecheesefactory.com/blog/retrofit-2.0/en

答案 4 :(得分:0)

如果有人带着科特琳/协程来到这里遇到同样的问题,请将错误处理程序添加到协程范围:

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

处理程序本身看起来像:

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

答案 5 :(得分:0)

科特林

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

定义您的改造界面:

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

答案 6 :(得分:0)

我发布此消息有两个原因:

  1. 我个人试图增加连接超时,但是从表面上看,它并不能真正解决问题的根本原因。此外,根据this post,用户不应等待超过10秒。
  2. 在现实世界的应用程序中,我们宁愿尽力以尽可能简洁的方式实施解决方案。

因此,这是我在 Kotlin 中提出的解决方案。它的灵感来自the answer提供的@Olcay Ertaş,并与Google's recommended architecture结合用于Android应用。

  1. 创建一个TimeoutInterceptor界面:

     interface TimeoutInterceptor : Interceptor
    
  2. 实现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
         }
     }
    
  3. 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's MVVM pattern):

     enum class ConnectionState {
         CONNECTED, NOT_CONNECTED, CONNECTION_TIMEOUT
     }
    
  2. 假设您的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)
     }
    
  3. EntityNetworkDataSource实现类中,您可以在SocketTimeoutException实现内部正确捕获fetchEntity(id: Int),如下所示:

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

相同的原理适用于您要拦截和捕获的任何连接异常。

答案 7 :(得分:0)

没有一个答案对我有用,但它们引导我走向正确的方向。这是我在 Kotlin 中的做法:

class ErrorInterceptor : Interceptor {

    override fun intercept(chain: Chain): Response = chain.run {
        try {
            proceed(
                request()
                    .newBuilder()
                    .build()
            )
        } catch (e: Exception) {
            var msg = ""
            val errorCode: Int

            when (e) {
                is SocketTimeoutException -> {
                    msg = // string message //
                    errorCode = // some int //
                }
                
               // Add additional error catching here //

            }

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

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

            } catch (e: Exception) {

                // Exceptions caught in ErrorInterceptor will propagate here

            }
        }
    }