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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:12:54