使用RxJava+Retrofit时,拦截器刷新服务端令牌致OkHttp请求泄漏
解决RxJava+Retrofit+OkHttp请求泄漏导致AsyncTask线程挂起的问题
我之前也踩过这个坑,尤其是在QA环境那种令牌每分钟刷新的极端场景下,线程池很快就被挂起的请求占满,网络Profiler里一堆未完成的请求,看着头大。咱们来一步步捋清楚问题根源和解决办法:
问题根源拆解
- 令牌刷新的竞态bug:当原始请求因令牌过期触发刷新时,如果没及时取消原始请求,它就会一直占着AsyncTask线程,死活不完成。QA环境令牌刷新太频繁,短时间内就会堆积大量这类“僵尸请求”,直接把线程池榨干。
- RxJava订阅没管好:原始请求的RxJava订阅如果在令牌刷新时没调用
dispose(),这个订阅会死死攥着AsyncTask线程的引用,导致线程没法被回收。 - OkHttp拦截器逻辑有问题:如果你的令牌刷新是在拦截器里同步处理的,没正确关闭原始请求的响应,就会让原始请求一直处于等待状态,永远卡着。
亲测有效的解决方案
1. 主动用RxJava的dispose()干掉过期请求
维护一个活跃请求的集合,令牌刷新时批量取消这些请求的订阅,释放线程资源:
// 用CompositeDisposable管理所有活跃请求的订阅 private CompositeDisposable activeRequests = new CompositeDisposable(); // 发起请求时把订阅加进去 Disposable requestDisposable = apiService.fetchData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( data -> handleSuccess(data), error -> handleError(error) ); activeRequests.add(requestDisposable); // 令牌刷新时清空所有活跃订阅 public void refreshTokenAndCleanup() { activeRequests.clear(); // 一键取消所有订阅,释放线程 // 执行令牌刷新逻辑 tokenService.refreshAuthToken() .subscribeOn(Schedulers.io()) .subscribe( newToken -> { // 令牌更新后重新发起之前的请求 reSendPendingRequests(); }, refreshError -> handleRefreshFailure(refreshError) ); }
2. 拦截器里异步处理令牌刷新,及时关闭原始响应
别在拦截器里同步阻塞处理刷新,改用异步逻辑,同时关闭原始请求的响应,避免线程挂起:
public class AuthInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); String currentToken = getCurrentValidToken(); // 先用当前令牌尝试请求 Response response = chain.proceed(originalRequest.newBuilder() .header("Authorization", "Bearer " + currentToken) .build()); if (response.code() == 401) { // 先关闭原始响应,释放资源 response.close(); // 异步刷新令牌,不阻塞当前线程 Completable.fromAction(this::refreshTokenSync) .subscribeOn(Schedulers.io()) .subscribe( () -> { // 刷新成功后重新发起请求 String newToken = getCurrentValidToken(); chain.proceed(originalRequest.newBuilder() .header("Authorization", "Bearer " + newToken) .build()); }, throwable -> handleRefreshError(throwable) ); // 返回一个终止响应,让原始请求结束 return new Response.Builder() .request(originalRequest) .protocol(Protocol.HTTP_1_1) .code(401) .message("Token expired, triggering refresh") .body(ResponseBody.create(MediaType.parse("text/plain"), "")) .build(); } return response; } private void refreshTokenSync() { // 同步执行令牌刷新逻辑(这里根据你的实际实现调整) } }
3. 换掉AsyncTask线程池,用自定义线程池
RxJava默认可能依赖AsyncTask的线程池,而这个线程池本身就容易出泄漏问题。给RxJava配置自定义线程池,彻底摆脱AsyncTask:
// 创建自定义线程池,按需调整核心线程数 ExecutorService customIoPool = Executors.newFixedThreadPool(6); // 发起请求时指定用自定义线程池 apiService.fetchData() .subscribeOn(Schedulers.from(customIoPool)) .observeOn(AndroidSchedulers.mainThread()) .subscribe(...);
4. 用LeakCanary精准定位泄漏点
如果还是找不到泄漏的具体位置,集成LeakCanary试试,它能帮你揪出到底是哪个对象死死抱着AsyncTask线程的引用——比如是不是某个订阅没取消,或者拦截器里的回调不小心持有了上下文。
验证方式
在QA环境模拟令牌每分钟刷新的场景,打开Android Studio Profiler监控:
- 检查网络请求是否都能正常完成,没有挂着的“僵尸请求”
- 看线程视图里的AsyncTask线程是否会被正常回收,不会越积越多
- 观察线程池的线程数量是否稳定,不会持续上涨
内容的提问来源于stack exchange,提问作者Herrbert74
相关产品推荐
相关产品推荐

