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

Android Jetpack Compose中TCP Socket发送消息时为空问题排查

问题

我正在开发一款基于Kotlin Jetpack Compose的Android应用,通过TCPHandler类的不同函数建立TCP连接、获取OutputStream,该类会保存Socket、输入输出流,再调用另一个函数发送消息。

问题:已成功连接指定IP和端口,但调用发送消息的函数时,Socket和OutputStream为空。

当前代码

TCPHandler类

class TCPHandler {
    private var socket: Socket? = null
    private var messageBytes: ByteArray? = null
    private var output: OutputStream? = null
    private var savedIpAdd = ""
    private var savedportNo = 0
    suspend fun connectToDevice(ipAddress: String, port: Int) {
        savedIpAdd = ipAddress
        savedportNo = port
        while (true){
            try {
                withContext(Dispatchers.IO) {
                    socket = Socket(ipAddress, port)
                    output = socket?.getOutputStream()
                    if (socket?.isConnected == true) {
                        Log.d("TCPdebug", "connected")
                        Log.d("TCPdebug", "$output")
                    } else if (socket == null) {
                        Log.d("TCPdebug", "TCP null")
                        Log.d("TCPdebug", "$output")
                    } else {
                        Log.d("TCPdebug", "Not Connect")
                        Log.d("TCPdebug", "$output")
                    }
                }
            } catch (e: IOException) {
                Log.e("MyApp", "An error occurred: ${e.message}")
                e.printStackTrace()
                delay(5000)
                socket?.close()
            }
        }
    }

    fun checkConnect(){
        Log.d("myTCP", "${socket?.isConnected}")
        Log.d("myTCP", "$output")
    }

    suspend fun processData(data: String) {
        withContext(Dispatchers.IO) {
            socket!!.close()
            Log.d("myTCP", "${socket?.isConnected}")
            Log.d("myTCP", "$output")
            if(socket != null && output != null){
                messageBytes = hexStringToBytes(data)
                output?.write(messageBytes)
                output?.flush()
            }else{
                socket?.close()
                socket = Socket(savedIpAdd, savedportNo)
                messageBytes = hexStringToBytes(data)
                output?.write(messageBytes)
                output?.flush()
            }
        }
    }

    fun closeConnection() {
        output?.close()
        socket?.close()
    }
}

ViewModel代码

class AppManagerViewModel : ViewModel() {
    
    private val tcpClient = TCPHandler()

    fun connectToDevice(serverAddress: String, port: Int) {
        viewModelScope.launch(Dispatchers.IO) {
            tcpClient.connectToDevice(deviceAddress, port)
        }
    }

    fun sendData(data: String) {
        viewModelScope.launch(Dispatchers.IO) {
            tcpClient.processData(data)
        }
    }

    fun checkConnection(){
        tcpClient.checkConnect()
    }

    fun closeConnection() {
        tcpClient.closeConnection()
    }

}

调用流程:在加载页调用connectToDevice,跳转至其他页面获取输入后返回加载页,调用sendData函数。此时processData开头的日志显示Socket为空,请问这是什么原因?


原因分析与修复方案

核心问题点

  1. processData强制关闭Socket:函数第一行socket!!.close()直接关闭当前连接,导致后续判断时Socket已失效,output流也随之不可用,这是日志显示Socket为空的直接原因。
  2. 连接函数无限循环重复创建Socket:connectToDevice中的while(true)会在连接成功后继续循环,反复创建新Socket覆盖旧实例,引发线程冲突或资源泄漏。
  3. ViewModel参数错误:connectToDevice接收的参数是serverAddress,但内部调用时用了未定义的deviceAddress,导致连接地址错误,甚至直接抛出异常。
  4. 线程安全缺失:socket、output等变量在多线程中读写,无同步机制,易出现状态不一致的竞态条件。
  5. 重连逻辑漏洞:processData的else分支中,创建新Socket后未重新获取OutputStream,直接使用旧对象,导致写入失败。

修复后的代码

修复后的TCPHandler

class TCPHandler {
    private var socket: Socket? = null
    private var output: OutputStream? = null
    private var savedIpAdd = ""
    private var savedportNo = 0
    // 线程安全锁
    private val lock = Any()

    suspend fun connectToDevice(ipAddress: String, port: Int) {
        savedIpAdd = ipAddress
        savedportNo = port
        // 已连接则直接返回
        synchronized(lock) {
            if (socket?.isConnected == true) return
        }
        while (true) {
            try {
                withContext(Dispatchers.IO) {
                    val newSocket = Socket(ipAddress, port)
                    val newOutput = newSocket.getOutputStream()
                    // 同步更新连接实例
                    synchronized(lock) {
                        socket = newSocket
                        output = newOutput
                    }
                    Log.d("TCPdebug", "connected")
                    Log.d("TCPdebug", "$output")
                    break // 连接成功退出循环
                }
            } catch (e: IOException) {
                Log.e("MyApp", "连接失败: ${e.message}")
                e.printStackTrace()
                delay(5000)
                // 清理失败连接
                synchronized(lock) {
                    socket?.close()
                    socket = null
                    output = null
                }
            }
        }
    }

    fun checkConnect() {
        synchronized(lock) {
            Log.d("myTCP", "${socket?.isConnected}")
            Log.d("myTCP", "$output")
        }
    }

    suspend fun processData(data: String) {
        withContext(Dispatchers.IO) {
            synchronized(lock) {
                // 检查连接有效性,无效则重连
                if (socket?.isConnected != true || output == null) {
                    try {
                        // 清理旧连接
                        socket?.close()
                        output?.close()
                        // 创建新连接并绑定输出流
                        socket = Socket(savedIpAdd, savedportNo)
                        output = socket?.getOutputStream()
                    } catch (e: IOException) {
                        Log.e("MyApp", "重连失败: ${e.message}")
                        e.printStackTrace()
                        return@withContext
                    }
                }
                // 发送数据
                val messageBytes = hexStringToBytes(data)
                output?.write(messageBytes)
                output?.flush()
                Log.d("myTCP", "数据发送成功")
            }
        }
    }

    fun closeConnection() {
        synchronized(lock) {
            output?.close()
            socket?.close()
            socket = null
            output = null
        }
    }

    // 补充十六进制转字节数组实现
    private fun hexStringToBytes(hex: String): ByteArray {
        val len = hex.length
        val data = ByteArray(len / 2)
        var i = 0
        while (i < len) {
            data[i / 2] = ((Character.digit(hex[i], 16) shl 4) + Character.digit(hex[i + 1], 16)).toByte()
            i += 2
        }
        return data
    }
}

修复后的ViewModel

class AppManagerViewModel : ViewModel() {
    
    private val tcpClient = TCPHandler()

    fun connectToDevice(serverAddress: String, port: Int) {
        viewModelScope.launch(Dispatchers.IO) {
            // 修正参数错误,使用传入的serverAddress
            tcpClient.connectToDevice(serverAddress, port)
        }
    }

    fun sendData(data: String) {
        viewModelScope.launch(Dispatchers.IO) {
            tcpClient.processData(data)
        }
    }

    fun checkConnection(){
        tcpClient.checkConnect()
    }

    fun closeConnection() {
        tcpClient.closeConnection()
    }

}

关键修复说明

  • 移除processData中强制关闭Socket的代码,改为先校验连接状态,无效则自动重连。
  • 添加synchronized锁保证多线程下变量操作的安全性。
  • 修正ViewModel中连接函数的参数错误,确保传入正确的IP地址。
  • 优化连接逻辑,成功连接后退出循环,仅在失败时重试,避免重复创建Socket。
  • 重连时重新绑定OutputStream,保证流与新Socket关联。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 11:53:18