You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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:

Core Idea: Use OkHttp Interceptors for Unified Handling

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 @Synchronized annotation on refreshAccessToken() ensures only one refresh call happens at a time, even if multiple requests hit INVALID_SESSION_ID simultaneously.
  • Retry Limits: We added a retryCount check 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:18:03