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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 08:50:29