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

