RxJava2中400响应处理及异常处理问题求助
我来帮你捋清楚这个问题——在OkHttp拦截器里用RxJava处理令牌刷新确实容易踩坑,尤其是异步逻辑和异常处理的衔接,咱们一步步来修正和完善。
一、先修正拦截器里的RxJava执行逻辑
你当前的代码直接调用subscribe(),这会导致令牌刷新在异步线程执行,但OkHttp的拦截器是同步阻塞的,也就是说拦截器会直接返回响应,而刷新令牌的逻辑还在后台跑,这肯定达不到你想要的“刷新后重试请求”的效果。
解决办法是用RxJava的阻塞型操作符,比如blockingSingle(),让拦截器等待刷新令牌的结果完成后再继续执行。同时,要把异常处理从doOnError转移到更可靠的错误处理链里,因为doOnError只是做副作用(比如打日志),不能改变流的走向。
修改后的核心代码大概是这样:
// 在拦截器的intercept方法中,当捕获到401响应时 try { TokenResponse tokenResponse = tokenRepository.refreshToken(authStateManager.current.refreshToken!!) .blockingSingle(); Log.d(TAG, "Obtained new access token: " + tokenResponse.toString()); authStateManager.updateAfterTokenResponse(TokenResponse.jsonDeserialize(tokenResponse.toString()), null); // 用新令牌重新构建请求并发起 Request newRequest = chain.request().newBuilder() .header("Authorization", "Bearer " + tokenResponse.accessToken) .build(); return chain.proceed(newRequest); } catch (Exception e) { Log.w(TAG, "Could not obtain new access token.", e); authStateManager.signOut(); // 这里可以返回一个自定义的错误响应,或者抛出异常让上层处理 return new Response.Builder() .code(401) .message("Token refresh failed") .request(chain.request()) .protocol(Protocol.HTTP_1_1) .body(ResponseBody.create(null, "")) .build(); }
二、异常处理的正确姿势
你之前用doOnError来处理刷新失败的情况,虽然能打日志和登出,但如果刷新过程中抛出异常,subscribe()的错误回调会触发,但拦截器已经无法感知这个错误了(因为异步)。用blockingSingle()配合try-catch可以同步捕获所有异常,确保拦截器能正确处理失败场景。
另外,如果不想用try-catch,也可以用RxJava的错误处理操作符,比如onErrorReturn或者onErrorResumeNext:
TokenResponse tokenResponse = tokenRepository.refreshToken(authStateManager.current.refreshToken!!) .onErrorResumeNext(throwable -> { Log.w(TAG, "Could not obtain new access token.", throwable); authStateManager.signOut(); // 抛出异常让blockingSingle捕获,或者返回一个无效的TokenResponse return Single.error(throwable); }) .blockingSingle();
三、如何处理400响应
400响应分两种场景,要区别对待:
1. 刷新令牌的请求返回400
这种情况通常意味着你的refreshToken已经失效(过期、被吊销、格式错误),这时候再重试刷新也没用,应该直接执行登出操作,并且不要再把原请求重试了。你可以在拦截器里判断当前请求是不是刷新令牌的请求,如果是且返回400,直接触发登出:
Response originalResponse = chain.proceed(request); if (originalResponse.code() == 400 && isRefreshTokenRequest(request)) { authStateManager.signOut(); // 返回错误响应给上层 return originalResponse.newBuilder() .message("Refresh token invalid") .build(); }
2. 普通API请求返回400
400是“Bad Request”,通常是参数错误、请求格式不对等问题,和认证无关,所以不需要触发令牌刷新逻辑,直接把原响应返回给上层即可,让业务层去处理参数错误的情况。
四、额外优化:避免并发刷新令牌
如果多个请求同时触发401,你的当前代码会多次调用refreshToken,这会造成不必要的重复请求。可以用一个原子布尔变量来标记是否正在刷新:
private AtomicBoolean isRefreshing = new AtomicBoolean(false); // 在拦截器中 if (originalResponse.code() == 401) { if (isRefreshing.compareAndSet(false, true)) { // 只有第一个请求会执行刷新 try { // 执行刷新逻辑... } finally { isRefreshing.set(false); } } else { // 其他请求等待刷新完成后,用新令牌重试 // 这里可以加个短暂等待,或者直接返回401让上层重试 return new Response.Builder() .code(401) .message("Token refreshing in progress") .request(chain.request()) .protocol(Protocol.HTTP_1_1) .body(ResponseBody.create(null, "")) .build(); } }
这样就能保证同一时间只有一个刷新令牌的请求在执行。
内容的提问来源于stack exchange,提问作者Smajl

