Jetpack Compose Kotlin蓝牙开发求助:UI通信与BluetoothService实现
解决Jetpack Compose蓝牙示例的UI同步与BluetoothService实现问题
一、将连接线程状态同步到UI界面
Jetpack Compose 推荐使用StateFlow实现跨线程状态同步,结合ViewModel可以安全地将后台线程的状态更新传递到UI。
步骤1:在ViewModel中定义状态流
import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch class BluetoothViewModel : ViewModel() { // 私有可变状态流,仅内部修改 private val _connectionStatus = MutableStateFlow("未连接") // 公开只读状态流,供UI收集 val connectionStatus = _connectionStatus.asStateFlow() // 供后台线程调用的状态更新方法,自动切换到主线程 fun updateStatus(status: String) { viewModelScope.launch { _connectionStatus.emit(status) } } }
步骤2:UI中收集状态流
使用collectAsStateWithLifecycle确保在组件生命周期内安全收集状态:
import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsStateWithLifecycle import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun BluetoothControlScreen() { val viewModel = viewModel<BluetoothViewModel>() val currentStatus = viewModel.connectionStatus.collectAsStateWithLifecycle() Text( text = "连接状态: ${currentStatus.value}", modifier = Modifier.padding(16.dp) ) // 其他UI元素(设备列表、连接按钮等) }
步骤3:在连接线程中更新状态
替换原有的Log.i()调用,直接调用ViewModel的updateStatus方法:
// 连接线程示例 Thread { try { // 执行蓝牙连接逻辑 viewModel.updateStatus("正在连接HC-05...") // 连接成功 viewModel.updateStatus("已连接到HC-05") } catch (e: IOException) { viewModel.updateStatus("连接失败: ${e.message}") } }.start()
二、实现可与UI交互的BluetoothService类
通过将BluetoothService与ViewModel绑定,结合协程处理后台IO操作,即可实现UI调用write方法、并将收发数据同步到UI的功能。
完整BluetoothService实现
import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothSocket import java.io.IOException import java.io.InputStream import java.io.OutputStream import java.util.UUID class BluetoothService(private val viewModel: BluetoothViewModel) { private var mmSocket: BluetoothSocket? = null private var mmOutputStream: OutputStream? = null private var mmInputStream: InputStream? = null private var readThread: Thread? = null // HC-05默认RFCOMM UUID private val HC05_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB") // 连接指定蓝牙设备 fun connect(device: BluetoothDevice) { try { viewModel.updateStatus("正在连接...") mmSocket = device.createRfcommSocketToServiceRecord(HC05_UUID) mmSocket?.connect() viewModel.updateStatus("连接成功") // 获取输入输出流 mmOutputStream = mmSocket?.outputStream mmInputStream = mmSocket?.inputStream // 启动数据读取线程 startReading() } catch (e: IOException) { viewModel.updateStatus("连接失败: ${e.message}") close() } } // UI直接调用此方法发送数据 fun write(data: ByteArray) { try { mmOutputStream?.write(data) viewModel.updateStatus("已发送: ${String(data)}") } catch (e: IOException) { viewModel.updateStatus("发送失败: ${e.message}") } } // 后台读取蓝牙设备数据 private fun startReading() { readThread = Thread { val buffer = ByteArray(1024) var bytesRead: Int while (!Thread.currentThread().isInterrupted) { try { bytesRead = mmInputStream?.read(buffer) ?: 0 val receivedData = String(buffer, 0, bytesRead) viewModel.updateStatus("收到数据: $receivedData") } catch (e: IOException) { viewModel.updateStatus("读取中断: ${e.message}") break } } } readThread?.start() } // 关闭连接 fun close() { try { mmInputStream?.close() mmOutputStream?.close() mmSocket?.close() viewModel.updateStatus("已断开连接") } catch (e: IOException) { viewModel.updateStatus("断开失败: ${e.message}") } readThread?.interrupt() readThread = null } }
在ViewModel中初始化并管理BluetoothService
class BluetoothViewModel : ViewModel() { // ... 之前的状态流定义 val bluetoothService = BluetoothService(this) // ViewModel销毁时自动关闭蓝牙连接 override fun onCleared() { super.onCleared() bluetoothService.close() } }
UI中调用write方法
通过ViewModel直接调用bluetoothService.write()即可发送数据:
import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable @Composable fun SendDataButton(viewModel: BluetoothViewModel) { Button(onClick = { val testData = "LED_ON".toByteArray() viewModel.bluetoothService.write(testData) }) { Text("发送开灯指令") } }
关键说明
- 避免使用传统
Handler:Jetpack Compose与协程、Flow的结合更符合现代Android开发规范,且能避免Handler常见的内存泄漏问题。 - 权限处理:确保已申请
BLUETOOTH_CONNECT权限,Android 12+还需在运行时动态申请。 - 线程安全:所有状态更新通过
viewModelScope切换到主线程,避免UI线程阻塞。
内容的提问来源于stack exchange,提问作者A. Oumnad
相关产品推荐
相关产品推荐

