Android View应用中优化ViewModel接收DataStore数据后的蓝牙连接逻辑
问题分析
原方案的核心问题在于:
- 两个
StateFlow单独收集,每次单个设置更新都会触发连接检查,当两个设置先后就绪时,会重复调用socket.connect() - 缺少连接状态校验,多次并发调用
connect()会导致蓝牙连接冲突,引发莫名断开 - Socket资源未与生命周期绑定,存在泄漏风险
优化方案
1. 合并流避免重复触发
使用combine操作符合并两个设置流,仅当两个设置均有效时才触发连接逻辑,从根源上减少重复调用。
2. 添加连接状态锁
通过isConnecting和isConnected变量严格控制连接操作的执行时机,防止并发冲突。
3. 规范IO线程调度
将蓝牙连接的阻塞操作放在Dispatchers.IO执行,避免阻塞主线程。
4. 生命周期绑定资源管理
页面销毁时主动关闭Socket,连接失败时清理无效资源,防止泄漏。
优化后代码示例
SettingsViewModel(可保持不变,或提前合并流)
@HiltViewModel class SettingsViewModel @Inject constructor( private val myRepository: MyRepository ) : ViewModel() { val setting1State: StateFlow<String> = myRepository.getSetting1().stateIn( viewModelScope, SharingStarted.WhileSubscribed(5000), "" ) val setting2State: StateFlow<String> = myRepository.getSetting2().stateIn( viewModelScope, SharingStarted.WhileSubscribed(5000), "" ) // 可选:在ViewModel提前合并流,简化Activity逻辑 val combinedSettings: StateFlow<Pair<String, String>> = combine(setting1State, setting2State) { s1, s2 -> Pair(s1, s2) }.stateIn( viewModelScope, SharingStarted.WhileSubscribed(5000), Pair("", "") ) }
MainActivity(核心优化)
@AndroidEntryPoint class MainActivity : AppCompatActivity() { private val settingsViewModel: SettingsViewModel by viewModels() private var socket: BluetoothSocket? = null private var isConnected = false private var isConnecting = false private var currentSettings: Pair<String, String>? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { // 合并两个设置流,仅当两者均非空时处理 settingsViewModel.combinedSettings.collect { (s1, s2) -> if (s1.isNotEmpty() && s2.isNotEmpty()) { val newSettings = Pair(s1, s2) // 仅当设置变化或未连接时尝试连接 if (currentSettings != newSettings || !isConnected) { currentSettings = newSettings // 若已连接,先断开再重建 if (isConnected) { lifecycleScope.launch(Dispatchers.IO) { socket?.close() socket = null isConnected = false } } attemptBluetoothConnection() } } } } } } private suspend fun attemptBluetoothConnection() { // 防止重复连接:正在连接或已连接时直接返回 if (isConnecting || isConnected) return isConnecting = true try { withContext(Dispatchers.IO) { // 延迟初始化Socket,确保线程安全 if (socket == null) { socket = createBluetoothSocket() // 替换为你的Socket创建逻辑 } socket?.connect() } isConnected = true // 连接成功后的逻辑(如更新UI、发送数据) } catch (e: IOException) { e.printStackTrace() withContext(Dispatchers.IO) { // 连接失败时清理无效Socket socket?.close() socket = null } isConnected = false } finally { isConnecting = false } } override fun onDestroy() { super.onDestroy() // 页面销毁时释放蓝牙资源 lifecycleScope.launch(Dispatchers.IO) { socket?.close() socket = null isConnected = false } } // 替换为你原有的Socket创建逻辑 private fun createBluetoothSocket(): BluetoothSocket? { // 示例:从BluetoothDevice获取RFCOMM Socket // return bluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString("你的服务UUID")) return null } }
核心优化点
- 流合并:
combine操作符确保只有两个设置均就绪时才触发连接,避免单个更新导致的重复调用 - 状态锁:
isConnecting和isConnected彻底杜绝并发连接请求,解决重复connect()引发的冲突 - IO线程隔离:蓝牙连接的阻塞操作放在
Dispatchers.IO,避免ANR - 资源清理:页面销毁时主动关闭Socket,连接失败时重置资源,防止泄漏
- 设置变更处理:支持设置更新时自动重建连接,适配业务需求
内容的提问来源于stack exchange,提问作者perissf
相关产品推荐
相关产品推荐

