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

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("发送开灯指令")
    }
}

关键说明

  1. 避免使用传统Handler:Jetpack Compose与协程、Flow的结合更符合现代Android开发规范,且能避免Handler常见的内存泄漏问题。
  2. 权限处理:确保已申请BLUETOOTH_CONNECT权限,Android 12+还需在运行时动态申请。
  3. 线程安全:所有状态更新通过viewModelScope切换到主线程,避免UI线程阻塞。

内容的提问来源于stack exchange,提问作者A. Oumnad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 15:26:30