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

socket.io/OkHttp程序关闭后线程泄漏问题求助

解决libGDX中Socket.IO客户端导致进程无法终止的问题

问题背景

在libGDX Java应用中使用io.socket:socket.io-client:2.1.0,桌面端关闭窗口后进程仍保持活跃;Android端关闭应用后,进程同样无法正常终止。仅在使用Socket.IO时出现该问题,即使按照官方FAQ说明关闭Socket并释放资源,仍无法解决。通过jstack [appProcessId]诊断,发现OkHttp TaskRunner和Okio Watchdog线程持续活跃。由于需发布至Play Store,不能使用System.exit(0)(该方法虽能临时解决,但不符合Android应用规范)。

当前配置与代码

build.gradle (core模块)

[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
eclipse.project.name = appName + '-core'

dependencies {
  api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
  api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
  api "com.badlogicgames.gdx:gdx:$gdxVersion"
  api "com.badlogicgames.gdx:gdx-tools:$gdxVersion"
  api ('com.squareup.okhttp3:okhttp:4.12.0')
  api ('io.socket:engine.io-client:2.1.0') {
    // excluding org.json which is provided by Android
    exclude group: 'org.json', module: 'json'
    exclude group: 'com.squareup.okhttp3', module: 'okhttp'
  }
  api ('io.socket:socket.io-client:2.1.0') {
    exclude group: 'com.squareup.okhttp3', module: 'okhttp'
  }
}

GameScreen.java 核心代码

MyGame game;

private Socket socketIo;
private Dispatcher dispatcher;
private OkHttpClient okHttpClient;

public class GameScreen extends InputAdapter implements Screen
{
    public GameScreen(final MyGame game) {
        this.game = game;

        URI uri = URI.create("wss://mydomain.app:8080");
        dispatcher = new Dispatcher();
        okHttpClient = new OkHttpClient.Builder()
            .dispatcher(dispatcher)
            .readTimeout(1, TimeUnit.MINUTES) // important for HTTP long-polling
            .build();
        IO.Options options = new IO.Options();
        options.callFactory = okHttpClient;
        options.webSocketFactory = okHttpClient;
        options.auth = singletonMap("serverOffset", "0");
        options.timeout = 10000;
        options.reconnectionAttempts = 3;
        socketIo = IO.socket(uri, options);
        socketIo.connect();
    }

    @Override
    public void dispose() {
        if(socketIo != null) {
            socketIo.disconnect();

            socketIo.off();
            socketIo.offAnyIncoming();
            socketIo.offAnyOutgoing();
            if(okHttpClient != null) {
                okHttpClient.connectionPool().evictAll();
                okHttpClient.dispatcher().cancelAll();
                okHttpClient.dispatcher().executorService().shutdownNow();
                okHttpClient.connectionSpecs().clear();
            }

            socketIo = null;
        }
    }
}

诊断结果

jstack输出显示残留活跃线程:

OkHttp TaskRunner
Okio Watchdog

解决方案

1. 完善Socket.IO连接终止流程

原代码仅调用disconnect(),该方法仅断开连接但未彻底释放底层资源。需替换为close() + destroy()的组合:

  • close():彻底关闭Socket会话,触发底层连接的关闭逻辑
  • destroy():销毁Socket实例,释放所有关联的监听器与内部资源

2. 彻底清理OkHttpClient资源

除现有操作外,需等待线程池终止,同时处理OkHttp连接池的线程:

  • 调用awaitTermination()等待线程池关闭,避免强制终止导致的资源泄漏
  • 单独关闭连接池的线程池,解决OkHttp TaskRunner残留问题

3. 修改后的dispose()方法

@Override
public void dispose() {
    // 先处理Socket.IO资源
    if(socketIo != null) {
        // 彻底关闭Socket连接
        socketIo.close();
        // 移除所有监听器
        socketIo.off();
        socketIo.offAnyIncoming();
        socketIo.offAnyOutgoing();
        // 销毁Socket实例,释放底层资源
        socketIo.destroy();
        socketIo = null;
    }

    // 再清理OkHttpClient资源
    if(okHttpClient != null) {
        // 取消所有待处理请求
        okHttpClient.dispatcher().cancelAll();
        
        // 关闭调度器线程池并等待终止
        ExecutorService dispatcherExecutor = okHttpClient.dispatcher().executorService();
        dispatcherExecutor.shutdownNow();
        try {
            if (!dispatcherExecutor.awaitTermination(2, TimeUnit.SECONDS)) {
                dispatcherExecutor.shutdownNow();
            }
        } catch (InterruptedException e) {
            dispatcherExecutor.shutdownNow();
            Thread.currentThread().interrupt();
        }
        
        // 清空并关闭连接池
        okHttpClient.connectionPool().evictAll();
        ExecutorService poolExecutor = okHttpClient.connectionPool().executorService();
        poolExecutor.shutdownNow();
        try {
            if (!poolExecutor.awaitTermination(2, TimeUnit.SECONDS)) {
                poolExecutor.shutdownNow();
            }
        } catch (InterruptedException e) {
            poolExecutor.shutdownNow();
            Thread.currentThread().interrupt();
        }
        
        okHttpClient = null;
    }
}

4. 额外注意事项

  • 确保在libGDX的ApplicationListener实现类的dispose()方法中,显式调用GameScreen的dispose(),避免屏幕切换时资源未被清理
  • 检查代码中是否存在其他持有Socket或OkHttpClient的引用,导致对象无法被GC回收
  • 尝试升级Socket.IO客户端至最新稳定版本(如2.1.1+),旧版本可能存在已知的资源泄漏bug

内容的提问来源于stack exchange,提问作者user2342558

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 06:52:45