Retrofit导致线程无法销毁问题求助
Retrofit独立应用线程无法销毁问题解决
问题描述
使用Retrofit 2.9.0的Kotlin独立应用,所有协程执行完毕、主线程退出后,通过Maven命令行运行主类时出现线程无法销毁的警告与异常,核心报错指向Okio Watchdog线程,调用dispatcher().executorService().shutdown()、cancelAll()等方法无效。
异常信息:
[WARNING] thread Thread[Okio Watchdog,5,com.divae.gitlab.MainKt] will linger despite being asked to die via interruption [WARNING] NOTE: 5 thread(s) did not finish despite being asked to via interruption. This is not a problem with exec:java, it is a problem with the running code. Although not serious, it should be remedied. [WARNING] Couldn't destroy threadgroup org.codehaus.mojo.exec.ExecJavaMojo$IsolatedThreadGroup[name=com.divae.gitlab.MainKt,maxpri=10] java.lang.IllegalThreadStateException at java.lang.ThreadGroup.destroy (ThreadGroup.java:795) ...(省略后续堆栈信息)
当前OkHttpClient初始化代码:
val connectionPool = ConnectionPool(5, 10, TimeUnit.SECONDS) val okhttpClient = OkHttpClient.Builder() .connectionPool(connectionPool) .addInterceptor(AuthInterceptor(privateToken)) .build() val retrofit = Retrofit.Builder() .baseUrl(baseURL) .addConverterFactory(GsonConverterFactory.create()) .client(okhttpClient) .build()
原因分析
OkHttp默认使用的是非守护线程(包括Dispatcher的线程池、ConnectionPool的线程、Okio的Watchdog线程),主线程退出时这些后台线程仍在运行,导致Maven的线程组无法销毁。之前的关闭操作仅处理了Dispatcher和ConnectionPool,但未覆盖Okio Watchdog线程,且shutdown()只是停止接受新任务,不会强制终止现有线程。
解决方案
方案1:自定义守护线程工厂(推荐)
将OkHttp所有线程设置为守护线程,主线程退出时自动销毁这些线程,从根源避免问题。
- 定义守护线程工厂:
class DaemonThreadFactory : ThreadFactory { private val defaultFactory = Executors.defaultThreadFactory() override fun newThread(r: Runnable): Thread { val thread = defaultFactory.newThread(r) thread.isDaemon = true // 标记为守护线程 return thread } }
- 构建OkHttpClient时指定线程工厂:
val daemonThreadFactory = DaemonThreadFactory() // 给连接池指定守护线程工厂 val connectionPool = ConnectionPool(5, 10, TimeUnit.SECONDS, daemonThreadFactory) val okhttpClient = OkHttpClient.Builder() .connectionPool(connectionPool) // 给调度器的线程池指定守护线程工厂 .dispatcher(Dispatcher(Executors.newCachedThreadPool(daemonThreadFactory))) .addInterceptor(AuthInterceptor(privateToken)) .build()
方案2:强制关闭所有相关线程
如果无法修改线程工厂,可在所有请求完成后强制关闭所有OkHttp相关线程,包括Okio Watchdog:
// 强制关闭调度器线程池并等待终止 okhttpClient.dispatcher().executorService().apply { shutdownNow() awaitTermination(1, TimeUnit.SECONDS) // 等待1秒确保线程终止 } okhttpClient.dispatcher().cancelAll() // 清空连接池并等待线程终止 okhttpClient.connectionPool().apply { evictAll() awaitTermination(1, TimeUnit.SECONDS) } // 关闭缓存(若启用) okhttpClient.cache()?.close() // 中断Okio Watchdog线程(通过反射) try { val watchdogField = Class.forName("okio.Okio").getDeclaredField("WATCHDOG") watchdogField.isAccessible = true val watchdogThread = watchdogField.get(null) as Thread watchdogThread.interrupt() } catch (e: Exception) { e.printStackTrace() // 反射失败时不影响主流程 }
方案3:升级OkHttp版本(可选)
OkHttp 4.x及以上版本对线程管理做了优化,默认线程行为更友好,若项目允许升级版本,可尝试将Retrofit和OkHttp升级到较新版本(如Retrofit 2.9.0对应OkHttp 4.9.0+),可能无需额外处理即可解决问题。
注意事项
- 所有关闭操作必须在所有网络请求完全结束后执行,例如在
runBlocking块的末尾,或所有协程join之后调用。 - 方案1是长期解决方案,方案2适合临时修复或无法修改线程工厂的场景。
内容的提问来源于stack exchange,提问作者Daniel Rusev
相关产品推荐
相关产品推荐

