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

如何在WebSocketListener中使用viewModelScope/lifecycleScope替代GlobalScope?

问题描述

我通过WebSocketListener实现了WebSocket功能,想用协程Channel传递事件,但当前Listener里没法直接用viewModelScope或lifecycleScope,只能用GlobalScope,担心这会导致内存泄漏和异常行为。

我的CustomWebSocketListener代码:

@OptIn(DelicateCoroutinesApi::class)
class CustomWebSocketListener : WebSocketListener() {
    val socketEventChannel: Channel<SocketUpdate> = Channel(10)

    override fun onOpen(webSocket: WebSocket, response: Response) {
        // no-op
    }

    override fun onMessage(webSocket: WebSocket, text: String) {
        GlobalScope.launch {
            socketEventChannel.sendOrNothing(SocketUpdate(text = text))
        }
    }

    override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
        GlobalScope.launch {
            socketEventChannel.sendOrNothing(SocketUpdate(exception = SocketAbortedException()))
        }
        webSocket.close(1000, null)
        socketEventChannel.close()
    }

    override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
        GlobalScope.launch {
            socketEventChannel.sendOrNothing(SocketUpdate(exception = t))
        }
    }

    private suspend fun <E> Channel<E>.sendOrNothing(e: E) {
        try {
            this.send(e)
        } catch (e: ClosedSendChannelException) {
            e.printStackTrace()
        }
    }
}

调用代码:

suspend fun startOrReloadSocket(url: String): Channel<SocketUpdate> {
    if(isConnected()) {
        if(url == webSocketUrl) {
            return webSocketListener!!.socketEventChannel
        } else {
            stopSocket()
        }
    }
    return with(CustomWebSocketListener()) {
        startOrReloadSocket(url,this)
        this@with.socketEventChannel
    }
}

注:startOrReloadSocket是在viewModelScope中调用的,希望让Listener和调用方处于同一协程作用域,用viewModelScope替代GlobalScope。

解决方案

核心思路是将调用方的协程作用域(这里是viewModelScope)传递给CustomWebSocketListener,让Listener内部使用该作用域启动协程,彻底摆脱GlobalScope的依赖。

1. 修改CustomWebSocketListener,接收外部协程作用域

给Listener添加构造参数,传入CoroutineScope,并在所有需要启动协程的地方使用这个作用域:

class CustomWebSocketListener(
    private val coroutineScope: CoroutineScope // 接收外部传入的作用域
) : WebSocketListener() {
    val socketEventChannel: Channel<SocketUpdate> = Channel(10)

    override fun onOpen(webSocket: WebSocket, response: Response) {
        // no-op
    }

    override fun onMessage(webSocket: WebSocket, text: String) {
        coroutineScope.launch { // 用传入的作用域替代GlobalScope
            socketEventChannel.sendOrNothing(SocketUpdate(text = text))
        }
    }

    override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
        coroutineScope.launch {
            socketEventChannel.sendOrNothing(SocketUpdate(exception = SocketAbortedException()))
        }
        webSocket.close(1000, null)
        socketEventChannel.close()
    }

    override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
        coroutineScope.launch {
            socketEventChannel.sendOrNothing(SocketUpdate(exception = t))
        }
    }

    private suspend fun <E> Channel<E>.sendOrNothing(e: E) {
        try {
            this.send(e)
        } catch (e: ClosedSendChannelException) {
            e.printStackTrace()
        }
    }
}

2. 在调用方传递viewModelScope

修改startOrReloadSocket函数,创建Listener实例时传入当前的viewModelScope:

// 假设该函数在ViewModel类中,拥有viewModelScope
suspend fun startOrReloadSocket(url: String): Channel<SocketUpdate> {
    if(isConnected()) {
        if(url == webSocketUrl) {
            return webSocketListener!!.socketEventChannel
        } else {
            stopSocket()
        }
    }
    return with(CustomWebSocketListener(viewModelScope)) { // 传入viewModelScope
        startOrReloadSocket(url,this)
        this@with.socketEventChannel
    }
}

3. 可选优化:绑定作用域生命周期,自动清理资源

为了进一步避免资源泄漏,可以让Listener监听作用域的取消事件,自动关闭WebSocket和Channel:

class CustomWebSocketListener(
    private val coroutineScope: CoroutineScope
) : WebSocketListener() {
    val socketEventChannel: Channel<SocketUpdate> = Channel(10)
    private var webSocket: WebSocket? = null

    init {
        // 监听作用域取消,自动清理资源
        coroutineScope.launch {
            coroutineScope.coroutineContext.job.join()
            webSocket?.close(1001, "Scope cancelled")
            socketEventChannel.close()
        }
    }

    override fun onOpen(webSocket: WebSocket, response: Response) {
        this.webSocket = webSocket // 保存WebSocket实例
    }

    // 其他方法保持不变
}

这样当viewModelScope因ViewModel销毁而取消时,Listener会自动关闭WebSocket和Channel,彻底杜绝内存泄漏风险。

内容的提问来源于stack exchange,提问作者c-an

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 00:24:17