Android Kotlin中Retrofit与AsyncHttpClient并发请求的刷新令牌同步处理
统一处理Retrofit+AsyncHttpClient的令牌过期逻辑(非Interceptor/Authenticator方案)
1. AsyncHttpClient onSuccess中检测令牌过期
因为服务器返回200状态码,必须在onSuccess回调里解析响应体判断令牌是否过期:
- 先定义通用基础响应类,适配所有接口返回格式:
data class BaseResponse<T>( val error_code: String?, val message: String?, val data: T? )
- 在AsyncHttpClient回调中解析并判断:
asyncHttpClient.get(url, new AsyncHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { String json = new String(responseBody); BaseResponse<?> response = new Gson().fromJson(json, BaseResponse.class); if ("TOKEN_EXPIRED".equals(response.getError_code())) { // 触发统一令牌刷新逻辑 TokenManager.getInstance().handleTokenExpired(() -> retryCurrentRequest()); } else { // 正常处理业务响应 handleSuccessResponse(response); } } @Override public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { // 处理其他请求失败场景 } });
2. 统一令牌管理核心类设计
实现单例TokenManager,集中处理令牌存储、过期检查、刷新、并发请求队列调度:
object TokenManager { private var accessToken: String? = null private var refreshToken: String? = null private var tokenExpireTime: Long = 0 // 过期时间戳(毫秒) private val requestQueue = ConcurrentLinkedQueue<() -> Unit>() private var isRefreshing = false private val lock = Any() // 更新令牌信息 fun updateTokens(newAccessToken: String, newRefreshToken: String, expireInSeconds: Long) { accessToken = newAccessToken refreshToken = newRefreshToken tokenExpireTime = System.currentTimeMillis() + expireInSeconds * 1000 } // 获取当前有效令牌(所有请求必须从这里拿,禁止缓存) fun getCurrentAccessToken(): String? = accessToken // 检查令牌是否已过期 fun isTokenExpired(): Boolean { return accessToken.isNullOrEmpty() || System.currentTimeMillis() >= tokenExpireTime } // 处理令牌过期,统一调度等待请求 fun handleTokenExpired(retryAction: () -> Unit) { synchronized(lock) { if (!isRefreshing) { isRefreshing = true requestQueue.add(retryAction) // 发起令牌刷新请求 refreshToken { success -> synchronized(lock) { isRefreshing = false if (success) { // 刷新成功,批量执行队列中所有请求 while (!requestQueue.isEmpty()) { requestQueue.poll()?.invoke() } } else { // 刷新失败,清空队列并通知UI requestQueue.clear() EventBus.getDefault().post(TokenRefreshFailedEvent()) } } } } else { // 正在刷新,直接加入队列等待 requestQueue.add(retryAction) } } } // 实际发起刷新令牌请求(可复用现有Retrofit/AsyncHttpClient实现) private fun refreshToken(callback: (Boolean) -> Unit) { val apiService = RetrofitClient.getInstance().create(AuthApi::class.java) apiService.refreshToken(refreshToken ?: "").enqueue(object : Callback<TokenResponse> { override fun onResponse(call: Call<TokenResponse>, response: Response<TokenResponse>) { if (response.isSuccessful && response.body() != null) { val tokenResp = response.body()!! updateTokens(tokenResp.accessToken, tokenResp.refreshToken, tokenResp.expireIn) callback(true) } else { callback(false) } } override fun onFailure(call: Call<TokenResponse>, t: Throwable) { callback(false) } }) } }
3. 请求前统一检查令牌逻辑
所有Retrofit/AsyncHttpClient请求发起前,必须先通过TokenManager做令牌检查:
Retrofit请求示例
fun fetchUserData() { if (TokenManager.isTokenExpired()) { TokenManager.handleTokenExpired { fetchUserData() } return } val token = TokenManager.getCurrentAccessToken() ?: return val apiService = RetrofitClient.getInstance().create(UserApi::class.java) apiService.getUserData("Bearer $token").enqueue(object : Callback<UserResponse> { override fun onResponse(call: Call<UserResponse>, response: Response<UserResponse>) { val baseResp = response.body() if ("TOKEN_EXPIRED".equals(baseResp?.error_code)) { TokenManager.handleTokenExpired { fetchUserData() } } else { // 处理业务数据 } } override fun onFailure(call: Call<UserResponse>, t: Throwable) { // 处理请求失败 } }) }
AsyncHttpClient请求示例
fun fetchOrderList() { if (TokenManager.getInstance().isTokenExpired()) { TokenManager.getInstance().handleTokenExpired(() -> fetchOrderList()); return; } String token = TokenManager.getInstance().getCurrentAccessToken(); if (token == null) return; AsyncHttpClient client = new AsyncHttpClient(); Request request = new Request.Builder() .url(ORDER_LIST_URL) .addHeader("Authorization", "Bearer " + token) .build(); client.execute(request, new AsyncHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { BaseResponse<OrderListResponse> resp = new Gson().fromJson(new String(responseBody), new TypeToken<BaseResponse<OrderListResponse>>(){}.getType()); if ("TOKEN_EXPIRED".equals(resp.getError_code())) { TokenManager.getInstance().handleTokenExpired(() -> fetchOrderList()); } else { // 处理订单数据 } } @Override public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { // 处理失败 } }); }
4. EventBus通知UI示例
定义事件类
class TokenRefreshFailedEvent(val message: String = "令牌刷新失败,请重新登录")
UI层注册与接收
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) EventBus.getDefault().register(this) } @Subscribe(threadMode = ThreadMode.MAIN) fun onTokenRefreshFailed(event: TokenRefreshFailedEvent) { Toast.makeText(this, event.message, Toast.LENGTH_LONG).show() // 跳转登录页等操作 startActivity(Intent(this, LoginActivity::class.java)) finish() } override fun onDestroy() { super.onDestroy() EventBus.getDefault().unregister(this) } }
5. 解决旧令牌重复使用问题
核心解决要点:
- 禁止缓存令牌:所有请求必须实时调用
TokenManager.getCurrentAccessToken()获取最新值,不能提前保存到局部变量。 - 全局令牌实时更新:
TokenManager.updateTokens()会立即替换存储的令牌,后续请求自动使用新值。 - 并发请求队列化:令牌过期时所有请求进入队列等待,刷新完成后统一用新令牌重发,避免旧令牌重复提交。
最佳实践建议
- 用单例模式确保
TokenManager全局唯一,避免多实例导致的令牌不一致。 - 加同步锁处理并发刷新逻辑,防止重复发起刷新请求。
- 给令牌刷新请求加重试次数限制(比如最多2次),避免无限循环。
- 若项目支持,可改用Coroutines或RxJava简化异步逻辑,替代回调嵌套。
内容的提问来源于stack exchange,提问作者Bingotop
相关产品推荐
相关产品推荐

