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
相关产品推荐
相关产品推荐

