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

Android Kotlin中Socket.IO的Ack回调在Future函数中未触发

Socket.IO Kotlin封装request函数时Ack回调不触发问题解决

问题场景

开发Android Kotlin项目时,使用Socket.IO与Node.js后端通信,封装了request函数用于发送事件并通过Ack回调获取结果,但函数内的Ack回调从未触发;直接调用socket.emit传入Ack回调却能正常工作。核心需求是不使用协程,通过Ack回调获取结果并赋值给变量。

问题代码

Kotlin客户端代码:

lateinit var socket: Socket

fun startMic() {
    val opts = IO.Options()
    opts.path = "/mic-io"
    opts.query = ""
    opts.forceNew = true
    opts.reconnection = true
    socket = IO.socket("http://192.168.1.3:9600", opts)
    if (!socket.connected()) {
        Log.i("MICTAG", "CONNECTING")
        socket.connect()
    }

    socket.off(Socket.EVENT_CONNECT).on(Socket.EVENT_CONNECT) {
        Log.i("MICTAG", "CONNECTED")
        val res = request("getRtpCapabilities")
        Log.i("MICTAG", "DONE")
    }
}

fun request(event: String): Array<Any?>? {
    val future = CompletableFuture<Array<Any?>?>()
    Log.i("MICTAG", "SENDING")
    socket.emit(event, Ack { data: Array<Any?>? ->
        // 永远不会执行到这里
        Log.i("MICTAG","new data using future")
        future.complete(data)
    })

    return future.join()
}

日志输出:

- CONNECTING
- CONNECTED
- SENDING

直接调用能正常触发回调的代码:

socket.emit("getRtpCapabilities", Ack { data: Array<Any?>? ->
    msg("new data using callback")
})

Node.js服务器代码:

socket.on('getRtpCapabilities', (callback) => {
    console.log('new data');
    callback({ ok: 'success' });
});

问题原因

request函数中调用future.join()会阻塞当前线程,而Socket.IO的Ack回调依赖同一个线程(Socket.IO事件循环线程)调度执行。当EVENT_CONNECT回调执行时,调用request后线程被join()阻塞,Ack回调无法被调度,形成死锁,因此永远无法进入Ack代码块。

直接调用socket.emit时没有阻塞线程,回调能被事件循环正常调度,所以可以正常触发。

解决方案

方案1:回调式设计(推荐)

避免阻塞线程,将request函数改为回调式,通过参数传递结果:

lateinit var socket: Socket

fun startMic() {
    val opts = IO.Options()
    opts.path = "/mic-io"
    opts.query = ""
    opts.forceNew = true
    opts.reconnection = true
    socket = IO.socket("http://192.168.1.3:9600", opts)
    if (!socket.connected()) {
        Log.i("MICTAG", "CONNECTING")
        socket.connect()
    }

    socket.off(Socket.EVENT_CONNECT).on(Socket.EVENT_CONNECT) {
        Log.i("MICTAG", "CONNECTED")
        // 通过回调获取结果
        request("getRtpCapabilities") { data ->
            Log.i("MICTAG", "DONE, data: ${data?.contentToString()}")
            // 在这里处理返回数据,比如赋值给变量
            val res = data
        }
    }
}

// 调整为带回调参数的request函数
fun request(event: String, onResult: (Array<Any?>?) -> Unit) {
    Log.i("MICTAG", "SENDING")
    socket.emit(event, Ack { data: Array<Any?>? ->
        Log.i("MICTAG","new data using callback")
        onResult(data)
    })
}

方案2:单独线程执行(不推荐)

如果一定要同步获取结果,可将emit操作放到单独线程,避免阻塞Socket.IO事件循环:

fun request(event: String): Array<Any?>? {
    val future = CompletableFuture<Array<Any?>?>()
    // 在独立线程中执行emit
    Thread {
        socket.emit(event, Ack { data: Array<Any?>? ->
            Log.i("MICTAG","new data using future")
            future.complete(data)
        })
    }.start()
    return future.join()
}

注意:方案2可能带来线程切换的额外问题(如UI操作需切回主线程),优先推荐回调式设计。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 16:35:27