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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 15:23:13