Android Retrofit 2:超时失败重试时如何通过编程方式动态增加超时时间
I get it—static timeout setups just don’t cut it when you need to bump the timeout only for retries after a timeout failure, then revert back to the original values. Let’s walk through two solid, practical approaches to solve this:
Approach 1: Manual Retry in onFailure() Callback
This is straightforward for one-off requests where you want explicit control over the retry flow.
First, save your original timeout values when setting up your OkHttpClient:
// Define your original timeout values (adjust these to your needs) private val originalConnectTimeout = 30L // Seconds private val originalReadTimeout = 30L private val originalWriteTimeout = 30L // Initialize your base OkHttpClient with original timeouts private var okHttpClient = OkHttpClient.Builder() .connectTimeout(originalConnectTimeout, TimeUnit.SECONDS) .readTimeout(originalReadTimeout, TimeUnit.SECONDS) .writeTimeout(originalWriteTimeout, TimeUnit.SECONDS) .build()
Then, in your onFailure() callback, check if the failure is a timeout, create a temporary client with extended timeouts, retry the request, and revert back to the original client afterward:
yourRetrofitCall.enqueue(object : Callback<YourResponseModel> { override fun onResponse(call: Call<YourResponseModel>, response: Response<YourResponseModel>) { // Handle successful response as usual } override fun onFailure(call: Call<YourResponseModel>, t: Throwable) { if (t is SocketTimeoutException) { // Calculate extended timeouts (add 20s to original values) val extendedConnectTimeout = originalConnectTimeout + 20 val extendedReadTimeout = originalReadTimeout + 20 val extendedWriteTimeout = originalWriteTimeout + 20 // Create a temporary OkHttpClient with extended timeouts val tempClient = okHttpClient.newBuilder() .connectTimeout(extendedConnectTimeout, TimeUnit.SECONDS) .readTimeout(extendedReadTimeout, TimeUnit.SECONDS) .writeTimeout(extendedWriteTimeout, TimeUnit.SECONDS) .build() // Retry the request with the temporary client tempClient.newCall(call.request()).enqueue(object : Callback<YourResponseModel> { override fun onResponse(call: Call<YourResponseModel>, response: Response<YourResponseModel>) { // Retry succeeded! Revert to original OkHttpClient resetToOriginalTimeouts() // Handle the successful retry response } override fun onFailure(call: Call<YourResponseModel>, t: Throwable) { // Retry failed, still revert to original timeouts resetToOriginalTimeouts() // Handle retry failure } }) } else { // Handle non-timeout failures normally } } }) // Helper method to reset OkHttpClient to original timeouts private fun resetToOriginalTimeouts() { okHttpClient = okHttpClient.newBuilder() .connectTimeout(originalConnectTimeout, TimeUnit.SECONDS) .readTimeout(originalReadTimeout, TimeUnit.SECONDS) .writeTimeout(originalWriteTimeout, TimeUnit.SECONDS) .build() }
Approach 2: Custom Interceptor for Reusable Retry Logic
If you want this behavior across multiple requests, a custom OkHttp Interceptor is cleaner and avoids code duplication.
Create an interceptor that handles retries with dynamic timeouts:
class DynamicTimeoutRetryInterceptor( private val originalConnectTimeout: Long, private val originalReadTimeout: Long, private val originalWriteTimeout: Long, private val maxRetries: Int = 1 // Adjust max retry count as needed ) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() var retryAttempt = 0 while (retryAttempt <= maxRetries) { try { // Use original timeouts for first attempt, extended for retries val currentChain = if (retryAttempt > 0) { val timeoutExtension = 20 * retryAttempt // 20s per retry chain.withConnectTimeout(originalConnectTimeout + timeoutExtension, TimeUnit.SECONDS) .withReadTimeout(originalReadTimeout + timeoutExtension, TimeUnit.SECONDS) .withWriteTimeout(originalWriteTimeout + timeoutExtension, TimeUnit.SECONDS) } else { chain } val response = currentChain.proceed(request) if (response.isSuccessful) { return response } } catch (e: SocketTimeoutException) { retryAttempt++ if (retryAttempt > maxRetries) { throw e // No more retries left, propagate the exception } } } throw IOException("Request failed after $maxRetries retries") } }
Add this interceptor to your OkHttpClient setup:
private val okHttpClient = OkHttpClient.Builder() .connectTimeout(originalConnectTimeout, TimeUnit.SECONDS) .readTimeout(originalReadTimeout, TimeUnit.SECONDS) .writeTimeout(originalWriteTimeout, TimeUnit.SECONDS) .addInterceptor(DynamicTimeoutRetryInterceptor( originalConnectTimeout, originalReadTimeout, originalWriteTimeout )) .build()
Key Notes
- Thread Safety: If you’re modifying the global OkHttpClient (like in Approach 1), make sure to handle thread safety if multiple requests are running concurrently. Alternatively, avoid modifying the global client and always create a new instance for retries.
- Retry Limits: Don’t forget to set a max retry count to avoid infinite loops if the endpoint is consistently timing out.
- Error Handling: Both approaches let you distinguish between timeout failures and other errors, so you only apply the extended timeout when it’s needed.
内容的提问来源于stack exchange,提问作者Prachi

