使用Retrofit的重试机制最佳实践及会话失效处理咨询
Great question—handling token refresh and retries across 20+ Retrofit interfaces without duplicating code is such a common pain point. Let’s break down the cleanest, most scalable best practice for this scenario:
Instead of writing error-checking logic in every single Callback (which would be a maintenance nightmare), we can leverage OkHttp’s interceptors to handle token refresh and retries globally. This way, every Retrofit request automatically gets this behavior without extra code per interface.
Step 1: Build an Auth Interceptor to Catch INVALID_SESSION_ID
This interceptor will attach the current access token to outgoing requests, check responses for the INVALID_SESSION_ID error, and trigger a token refresh + retry when needed.
class AuthInterceptor(private val tokenManager: TokenManager) : Interceptor { // Track retry count to avoid infinite loops private var retryCount = 0 override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() // Attach current access token to the request val authenticatedRequest = originalRequest.newBuilder() .header("Authorization", "Bearer ${tokenManager.getAccessToken()}") .build() val response = chain.proceed(authenticatedRequest) // Check if we got the INVALID_SESSION_ID error (adjust based on your API's actual response) if (response.code == 401 && response.peekBody(Long.MAX_VALUE).string().contains("INVALID_SESSION_ID") && retryCount < 1) { retryCount++ // Close the original response to prevent resource leaks response.close() // Sync refresh token (critical: must be sync here since interceptors run on OkHttp's thread) val newAccessToken = tokenManager.refreshAccessToken() newAccessToken?.let { // Rebuild request with fresh token and retry val refreshedRequest = originalRequest.newBuilder() .header("Authorization", "Bearer $it") .build() return chain.proceed(refreshedRequest) } } // Reset retry count for subsequent requests retryCount = 0 return response } }
Step 2: Create a TokenManager to Handle Token Logic
This class encapsulates all token-related operations—storing, retrieving, and refreshing tokens. It ensures we don’t duplicate this logic anywhere else, and uses synchronization to prevent concurrent refresh calls.
class TokenManager(private val authApi: AuthApi) { private var accessToken: String? = null private var refreshToken: String? = null // Use SharedPreferences or secure storage to persist tokens private val prefs = MyApplication.instance.getSharedPreferences("auth_prefs", Context.MODE_PRIVATE) init { // Load saved tokens on initialization accessToken = prefs.getString("access_token", null) refreshToken = prefs.getString("refresh_token", null) } fun getAccessToken(): String? = accessToken // Synchronized to avoid multiple concurrent refresh requests @Synchronized fun refreshAccessToken(): String? { return try { // Make a SYNC Retrofit call to get new tokens val refreshResponse = authApi.refreshToken(refreshToken!!).execute() if (refreshResponse.isSuccessful) { val newTokens = refreshResponse.body() accessToken = newTokens?.accessToken refreshToken = newTokens?.refreshToken // Save new tokens to storage prefs.edit() .putString("access_token", accessToken) .putString("refresh_token", refreshToken) .apply() accessToken } else { // Refresh failed—likely means refresh token is also invalid null } } catch (e: Exception) { e.printStackTrace() null } } }
Step 3: Wire It All Up to Your Retrofit Instance
First, create a "raw" Retrofit instance to initialize the TokenManager (to avoid circular dependencies), then build your final OkHttpClient with the interceptor, and create all your interface instances from this final client.
// 1. Create a base Retrofit instance without the interceptor val baseRetrofit = Retrofit.Builder() .baseUrl("https://your-api-base-url.com/") .addConverterFactory(GsonConverterFactory.create()) .build() // 2. Initialize TokenManager with the auth API val authApi = baseRetrofit.create(AuthApi::class.java) val tokenManager = TokenManager(authApi) // 3. Build OkHttpClient with the interceptor val okHttpClient = OkHttpClient.Builder() .addInterceptor(AuthInterceptor(tokenManager)) .build() // 4. Create your final Retrofit instance val retrofit = Retrofit.Builder() .baseUrl("https://your-api-base-url.com/") .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build() // Now create all your 20+ interfaces with this retrofit instance val updateUserApi = retrofit.create(UpdateUserAPI::class.java) val someOtherApi = retrofit.create(SomeOtherAPI::class.java) // ... and so on
Key Notes for Production
- Handle Refresh Failures: If
refreshAccessToken()returns null, you should trigger a redirect to your login screen. You can do this by throwing a custom exception in the interceptor and catching it in a global error handler. - Concurrent Requests: The
@Synchronizedannotation onrefreshAccessToken()ensures only one refresh call happens at a time, even if multiple requests hitINVALID_SESSION_IDsimultaneously. - Retry Limits: We added a
retryCountcheck to avoid infinite loops if the refreshed token still fails (e.g., a bug in the API). Adjust the limit based on your needs. - Callback Compatibility: This works with Retrofit’s traditional Callback pattern and Coroutines/Flow—no changes needed to your existing interface definitions.
This approach keeps your code DRY (Don’t Repeat Yourself) and makes future changes to token logic trivial—you only need to update the interceptor or TokenManager instead of 20+ callbacks.
内容的提问来源于stack exchange,提问作者Gokul Nath KP

