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为空,请问这是什么原因?
原因分析与修复方案
核心问题点
processData强制关闭Socket:函数第一行socket!!.close()直接关闭当前连接,导致后续判断时Socket已失效,output流也随之不可用,这是日志显示Socket为空的直接原因。- 连接函数无限循环重复创建Socket:
connectToDevice中的while(true)会在连接成功后继续循环,反复创建新Socket覆盖旧实例,引发线程冲突或资源泄漏。 - ViewModel参数错误:
connectToDevice接收的参数是serverAddress,但内部调用时用了未定义的deviceAddress,导致连接地址错误,甚至直接抛出异常。 - 线程安全缺失:
socket、output等变量在多线程中读写,无同步机制,易出现状态不一致的竞态条件。 - 重连逻辑漏洞:
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
相关产品推荐
相关产品推荐

