Kotlin Android蓝牙实时读传感器数据卡顿问题解决方案咨询
问题根源分析
你的APP卡顿不是Handler的锅,核心问题是把蓝牙IO读取这种耗时阻塞操作放在了主线程。默认创建的Handler绑定的是主线程Looper,所以你在Runnable里调用的readSensors()会在主线程执行——里面的inputStream.read()是阻塞式IO操作,加上while循环会持续占用主线程,导致UI渲染、用户输入等操作无法及时响应。
优化方案
方案1:线程+Handler(适配现有逻辑)
把耗时的蓝牙读取操作放到子线程,主线程只负责接收数据、更新UI,避免阻塞主线程:
修改代码示例
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.control_layout) m_address = intent.getStringExtra(BluetoothActivity.EXTRA_ADDRESS)!! ConnectToDevice(this).execute() // 主线程Handler,用于接收子线程传来的数据 val mainHandler = Handler(Looper.getMainLooper()) // 开启子线程循环读取传感器数据 Thread { while (m_isConnected) { readSensors() // 数据读取完成后,通过Handler通知主线程(如果需要更新UI) mainHandler.post { // 这里可以更新UI,比如把arrVal的数据显示到界面 } Thread.sleep(2000) // 间隔2秒读取一次 } }.start() Handler(Looper.getMainLooper()).postDelayed({ val intent = Intent(this, MainActivity::class.java) finish() startActivity(intent) }, 5000) } // 这个函数现在在子线程执行,无需担心阻塞主线程 private fun readSensors() { val bluetoothSocketInputStream = m_bluetoothSocket?.inputStream ?: return val delim = "," var readMessage: String = "" try { // 注意:原代码的while循环会阻塞到读到'*',如果Arduino未发送该字符,线程会一直卡着 // 建议改为单次读取或设置超时,避免无限阻塞 val bytes = bluetoothSocketInputStream.read(buffer) if (bytes > 0) { readMessage = String(buffer, 0, bytes) if (readMessage.contains('*')) { arrVal = readMessage.split(delim).toTypedArray() } } } catch (e: IOException) { e.printStackTrace() } }
方案2:Kotlin协程(更简洁的异步处理)
用Kotlin协程的IO调度器处理耗时IO操作,主线程安全更新数据,无需手动管理线程:
修改代码示例
import kotlinx.coroutines.* override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.control_layout) m_address = intent.getStringExtra(BluetoothActivity.EXTRA_ADDRESS)!! ConnectToDevice(this).execute() // 启动协程,在IO线程执行读取操作 lifecycleScope.launch(Dispatchers.IO) { while (m_isConnected) { readSensors() // 如果需要在主线程更新UI,切换到Main调度器 withContext(Dispatchers.Main) { // 更新UI逻辑 } delay(2000) // 协程的非阻塞延迟 } } lifecycleScope.launch(Dispatchers.Main) { delay(5000) val intent = Intent(this@YourActivityName, MainActivity::class.java) finish() startActivity(intent) } } private fun readSensors() { // 逻辑同方案1,此时函数在IO线程执行,不会阻塞主线程 val bluetoothSocketInputStream = m_bluetoothSocket?.inputStream ?: return val delim = "," var readMessage: String = "" try { val bytes = bluetoothSocketInputStream.read(buffer) if (bytes > 0) { readMessage = String(buffer, 0, bytes) if (readMessage.contains('*')) { arrVal = readMessage.split(delim).toTypedArray() } } } catch (e: IOException) { e.printStackTrace() } }
方案3:官方推荐的蓝牙持续监听线程
Android官方蓝牙示例(BluetoothChat)会开启专门的输入线程,持续监听蓝牙输入流,数据到达时立即处理,比定时轮询更高效:
代码示例
private var readThread: Thread? = null override fun onResume() { super.onResume() // 连接成功后启动读取线程 if (m_isConnected && readThread == null) { readThread = Thread { val inputStream = m_bluetoothSocket?.inputStream ?: return@Thread val buffer = ByteArray(1024) var bytes: Int while (m_isConnected) { try { // 持续读取流数据 bytes = inputStream.read(buffer) if (bytes > 0) { val readMessage = String(buffer, 0, bytes) if (readMessage.contains('*')) { val arrVal = readMessage.split(",").toTypedArray() // 通过Handler传回主线程更新数据/UI Handler(Looper.getMainLooper()).post { this@YourActivityName.arrVal = arrVal // 更新UI } } } } catch (e: IOException) { e.printStackTrace() break } } } readThread?.start() } } override fun onPause() { super.onPause() // 页面暂停时停止线程,避免内存泄漏 m_isConnected = false readThread?.interrupt() readThread = null }
额外注意事项
companion object中的arrVal是全局可变变量,多线程访问时要注意线程安全,建议用@Volatile修饰或者用线程安全的集合。- 原
readSensors()中的while循环会一直阻塞直到读到*,如果Arduino未发送该字符,线程会无限阻塞,建议添加超时机制或改为单次读取。 ConnectToDevice如果是AsyncTask,建议替换为协程或线程,因为AsyncTask已被废弃。
内容的提问来源于stack exchange,提问作者Nate
相关产品推荐
相关产品推荐

