Retrofit 2:捕获连接超时异常
Retrofit 2: Catch connection timeout exception
我有以下设置:
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
有没有办法将其路由到改造失败方法中,以便我可以在那里处理它?
提前致谢!
显然异常确实被包装到 RetrofitException 中,因此您可以在失败方法中处理它。
有点复杂。使用 Retrofit,您可以进行 API 同步或异步调用。
如果您的端点 returns 无效且有回调,则它是异步的。
如果它 returns 没有回调它是同步的。
对于异步调用,您会在回调的 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
改造 2
在您的网络服务实例中定义一个侦听器:
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());
}
@Override
public void onFailure(Call call, Throwable t) {
if(t instanceof SocketTimeoutException){
message = "Socket Time out. Please try again.";
}
}
如果有人 Kotlin/Coroutines 遇到同样的问题,请将错误处理程序添加到您的协程范围:
CoroutineScope(Dispatchers.IO).launch(handler) {
而处理程序本身看起来像:
val handler = CoroutineExceptionHandler { _, exception ->
Log.t("Network", "Caught $exception")
}
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。
我发布这个有两个原因:
- 我个人尝试增加连接超时,但最终并没有真正从根本上解决问题。此外,根据 this post.
,用户不应等待超过 10 秒
- 在现实世界的应用程序中,我们宁愿尽最大努力以尽可能干净的方式实施解决方案。
所以,这是我在 Kotlin 中提出的解决方案。它的灵感来自 provided by @Olcay Ertaş and combined with Google's recommended architecture for Android apps。
创建一个TimeoutInterceptor
接口:
interface TimeoutInterceptor : Interceptor
实现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
}
}
在您的 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 秒作为默认值
创建枚举 class ConnectionState
。它将提供一个枚举常量对象 CONNECTION_TIMEOUT
,它将进一步用于将适当的连接(或 API 调用)状态从 EntityNetworkDataSource
class 传送到视图 class(如果你关注Google's MVVM pattern):
enum class ConnectionState {
CONNECTED, NOT_CONNECTED, CONNECTION_TIMEOUT
}
假设您的 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)
}
在EntityNetworkDataSource
实现class中,你可以正确捕获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)
}
}
}
同样的原则适用于您想要拦截和捕获的任何连接异常。
None 的答案对我很有用,但它们引导我朝着正确的方向前进。看看下面我是如何在 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
}
}
}
我有以下设置:
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
有没有办法将其路由到改造失败方法中,以便我可以在那里处理它?
提前致谢!
显然异常确实被包装到 RetrofitException 中,因此您可以在失败方法中处理它。
有点复杂。使用 Retrofit,您可以进行 API 同步或异步调用。
如果您的端点 returns 无效且有回调,则它是异步的。 如果它 returns 没有回调它是同步的。
对于异步调用,您会在回调的 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
改造 2
在您的网络服务实例中定义一个侦听器:
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());
}
@Override
public void onFailure(Call call, Throwable t) {
if(t instanceof SocketTimeoutException){
message = "Socket Time out. Please try again.";
}
}
如果有人 Kotlin/Coroutines 遇到同样的问题,请将错误处理程序添加到您的协程范围:
CoroutineScope(Dispatchers.IO).launch(handler) {
而处理程序本身看起来像:
val handler = CoroutineExceptionHandler { _, exception ->
Log.t("Network", "Caught $exception")
}
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。
我发布这个有两个原因:
- 我个人尝试增加连接超时,但最终并没有真正从根本上解决问题。此外,根据 this post. ,用户不应等待超过 10 秒
- 在现实世界的应用程序中,我们宁愿尽最大努力以尽可能干净的方式实施解决方案。
所以,这是我在 Kotlin 中提出的解决方案。它的灵感来自
创建一个
TimeoutInterceptor
接口:interface TimeoutInterceptor : Interceptor
实现
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 } }
在您的
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 秒作为默认值创建枚举 class
ConnectionState
。它将提供一个枚举常量对象CONNECTION_TIMEOUT
,它将进一步用于将适当的连接(或 API 调用)状态从EntityNetworkDataSource
class 传送到视图 class(如果你关注Google's MVVM pattern):enum class ConnectionState { CONNECTED, NOT_CONNECTED, CONNECTION_TIMEOUT }
假设您的
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) }
在
EntityNetworkDataSource
实现class中,你可以正确捕获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) } } }
同样的原则适用于您想要拦截和捕获的任何连接异常。
None 的答案对我很有用,但它们引导我朝着正确的方向前进。看看下面我是如何在 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
}
}
}