安卓Kivy应用蓝牙发送报错及InputStream多线程读取问题咨询
蓝牙功能报错与实现修复
发送数据报错的根源
OutputStream.write()仅支持接收Java byte[]类型参数,原实现中已完成转换的Python字节对象,被通过字符串格式化包装成了普通字符串传入,直接触发类型不兼容报错。
修正要点
- 字符串转字节数组的逻辑本身无错误,不要对已转换的字节对象再做字符串包装
- Python bytes对象可被jnius自动映射为Java可识别的byte数组,可直接传入write方法
- 统一发送逻辑,不要混用pyserial与Java层socket的流对象,避免跨平台适配混乱
修正后的坐标发送代码:
def SendCoordinates(self, XCoordinate, YCoordinate): # 直接在原始字符串层拼接换行符,不要在字节层做字符串格式化 StrPositionData = f"{XCoordinate}, {YCoordinate}\n" ByteString = bytes(StrPositionData, 'utf-8') # 直接传入转换完成的字节数组,不额外做字符串包装 self.SendSerialData.write(ByteString) self.SendSerialData.flush()
同步修正触摸抬起时的停止指令发送逻辑:安卓端不存在pyserial实例的bluetooth_device对象,统一使用已初始化的输出流发送指令:
def on_touch_up(self,touch): if self.BluetoothInit == 1: stop_cmd = bytes("STOP\n", 'utf-8') self.SendSerialData.write(stop_cmd) self.SendSerialData.flush()
后台线程读取蓝牙返回数据的实现
Java InputStream.read()为阻塞式调用,必须放在非UI线程运行,否则会完全卡死Kivy界面。Python原生threading模块即可实现该需求,无需额外依赖。
实现流程
- 提前在类属性中定义线程运行标志、线程实例,避免连接断开后线程无意义空转
- 编写独立的后台读取循环,分配固定大小的缓冲区作为
read()方法的入参 - 子线程无法直接修改Kivy UI组件属性,读到数据后通过
Clock.schedule_once将数据投递回主线程完成界面更新
核心实现代码:
import threading class HomeScreen(Screen): # 新增线程相关类属性 receive_thread = None is_running = False # 后台线程运行的读取循环,严禁直接操作UI组件 def _bluetooth_read_loop(self): # 分配1024字节缓冲区,匹配Java read()的入参要求 buffer = bytearray(1024) while self.is_running: try: # 调用InputStream.read(),返回实际读取到的有效字节长度 bytes_read = self.ReceiveSerialData.read(buffer) if bytes_read > 0: # 截取有效长度的字节转成可读字符串 received_str = buffer[:bytes_read].decode('utf-8', errors='ignore') # 将数据投递回主线程做界面刷新 Clock.schedule_once( lambda dt, data=received_str: self._update_voltage_display(data), 0 ) except Exception: # 连接异常时自动退出读取循环 self.is_running = False break # 主线程执行的UI更新方法 def _update_voltage_display(self, received_data): # 将读取到的内容更新到电压输入框 self.mvoltage.text = received_data.strip()
在蓝牙连接流程中,等socket建立、输入输出流初始化完成后,启动读取线程:
def ConnectBluetooth(self): # Windows端pyserial分支逻辑保持不变 try: # ...原有安卓端配对设备、匹配HC-05、初始化socket的逻辑保持不变 socket.connect() # 启动后台读取线程 self.is_running = True self.receive_thread = threading.Thread( target=self._bluetooth_read_loop, daemon=True # 设为*守护线程*,应用退出时自动销毁 ) self.receive_thread.start() self.BluetoothInit = 1 except Exception: raise Exception('无法完成蓝牙连接')
原代码存在的其他逻辑/语法问题
- 属性与方法重名覆盖:原实现将
ReceiveSerialData同时作为InputStream实例属性、读取方法名使用,实例赋值后会直接覆盖类方法,后续无法正常调用读取逻辑,必须将属性名与方法名严格区分 - 缩进错误:原代码中
if self.BluetoothInit == 1:下的读取语句未做缩进,直接触发Python语法报错 - UI线程阻塞风险:蓝牙
socket.connect()本身也是阻塞调用,建议将连接流程也放到后台线程执行,避免点击连接按钮时界面卡顿
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

