UsbDeviceConnection.requestWait()异常:USB设备重连后无法恢复通信
问题背景
重构Android应用USB通信模块时,原实现采用同步方法UsbDeviceConnection.bulkTransfer()完成端点间数据传输,重构后改为组合调用UsbRequest.initialize()、UsbRequest.queue()、UsbDeviceConnection.requestWait()的实现。新实现在平板上冷启动运行正常,性能优于原实现,但外部设备发生USB中断、断开连接后,即使设备重新接入,新方案也无法恢复通信,旧的bulkTransfer()实现则可以在重连后正常工作。
相关实现代码
基于bulkTransfer()的旧实现
byte[] response = new byte[64]; int result = 0; int tries = 0; while (result < 1){ if (tries > 5) { Log.d("PayloadTask", "PayloadTask Timed Out."); return null; } else if (isCancelled()) { Log.w("PayloadTask", "PayloadTask Cancelled!"); return null; } /*synchronized (usbConnection)*/ result = usbConnection.bulkTransfer(usbFromAggBoard, response, response.length, 25); tries += 1; } Log.i("PayloadTask", new Integer(response.length).toString() + " bytes received: "); Log.d("PayloadTask", "doInBackground returning some payload from the aggboard."); return Payload.CreatePayload(response);
基于UsbRequest/requestWait()的重构实现
byte[] response = new byte[64]; int result = 0; int tries = 0; while (result < 1){ if (tries > 5) { Log.d("PayloadTask", "PayloadTask Timed Out."); return null; } else if (isCancelled()) { Log.w("PayloadTask", "PayloadTask Cancelled!"); return null; } /*synchronized (usbConnection)*/ if (usbConnection != null) { UsbRequest request = new UsbRequest(); ByteBuffer byteBuffer = ByteBuffer.wrap(response); byteBuffer.rewind(); try { request.initialize(usbConnection, usbFromAggBoard); if (!request.queue(byteBuffer, response.length)) { throw new IOException("Error queueing USB request."); } usbConnection.requestWait(); } catch (IOException e) { e.printStackTrace(); } finally { if(usbConnection != null) { result = request.hashCode(); request.close(); } else{ Log.d("PayloadTask", "Request not received by usbConnection"); } } } tries += 1; } Log.i("PayloadTask", new Integer(response.length).toString() + " bytes received: "); Log.d("PayloadTask", "doInBackground returning some payload from the aggboard."); return Payload.CreatePayload(response);
问题根因
UsbRequest是和USB连接、端点强绑定的有状态对象,和无状态同步调用的bulkTransfer()生命周期逻辑完全不同,重构代码存在多处逻辑错误直接导致重连失效:
- 连接有效性判断逻辑失效:仅判断
usbConnection对象是否非空,但设备断开重连后,旧的UsbDeviceConnection、UsbEndpoint实例已经和新设备解绑,哪怕对象不为空也属于失效会话。bulkTransfer()内部会自动校验连接有效性,失效时直接返回错误码触发重试,但UsbRequest绑定到失效连接/端点时不会第一时间抛出明确错误,后续队列、等待操作会静默失败。 - 传输结果判断完全错误:finally块中直接将
result赋值为request.hashCode(),对象哈希值永远为正整数,导致result < 1的循环退出条件完全失效。哪怕请求实际传输失败,只要request对象创建成功,循环就会直接退出,根本没有拿到有效返回数据。 - 未做请求返回值校验:没有校验
requestWait()返回的请求对象是否为当前入队的request,设备断连时该方法可能返回null、或者队列中残留的其他无效请求,这些异常分支都没有处理,会导致无效请求残留在连接队列中,重连后阻塞新请求。 - 缺少断连后的资源重置逻辑:设备断开时没有主动关闭持有的失效UsbRequest、释放旧连接,重连后继续复用断连前的旧连接、旧端点实例,没有重新走权限申请、接口占用、端点枚举流程,自然无法恢复通信。
修复方案
- 注册USB设备连接状态广播:收到
ACTION_USB_DEVICE_DETACHED广播时,立刻关闭所有持有的UsbRequest、调用usbConnection.close()释放连接,将全局保存的usbConnection、usbFromAggBoard实例置空,清空待处理请求队列。 - 重连流程补全:收到
ACTION_USB_DEVICE_ATTACHED广播,或者传输前检测到连接失效时,重新走USB设备权限申请、接口claim、端点枚举流程,拿到全新的连接和端点实例后再发起传输,禁止复用断连前的旧实例。 - 修正传输结果判断逻辑:不要用request对象的哈希值作为传输结果,校验
requestWait()返回的request对象是否为当前入队的实例,匹配成功后从ByteBuffer的position属性拿到实际接收的字节数,字节数大于0才判定为传输成功,作为循环退出的依据。 - 补全异常分支处理:
request.initialize()、request.queue()、requestWait()任意一步返回false、null或者抛出异常,立刻关闭当前request,标记连接为失效状态触发重置流程,不要静默吞掉异常继续用无效实例重试。
修正后的核心传输逻辑参考
byte[] response = new byte[64]; int result = 0; int tries = 0; while (result < 1){ if (tries > 5) { Log.d("PayloadTask", "PayloadTask Timed Out."); return null; } else if (isCancelled()) { Log.w("PayloadTask", "PayloadTask Cancelled!"); return null; } // 前置校验连接有效性,无效则触发重连 if (!isUsbConnectionValid()) { resetUsbConnection(); tries++; try { Thread.sleep(30); } catch (InterruptedException e) { return null; } continue; } UsbRequest request = new UsbRequest(); ByteBuffer byteBuffer = ByteBuffer.allocate(64); try { if (!request.initialize(usbConnection, usbFromAggBoard)) { throw new IOException("UsbRequest init failed, connection invalid"); } if (!request.queue(byteBuffer, 64)) { throw new IOException("UsbRequest queue failed"); } UsbRequest finishedReq = usbConnection.requestWait(); // 校验返回请求匹配当前提交的请求 if (finishedReq != request) { if (finishedReq != null) finishedReq.close(); throw new IOException("Wait returned invalid request"); } // 读取实际传输字节长度 result = byteBuffer.position(); if (result > 0) { byteBuffer.flip(); byteBuffer.get(response, 0, result); } } catch (Exception e) { Log.e("PayloadTask", "USB transfer failed", e); markConnectionInvalid(); } finally { request.close(); } tries++; } Log.i("PayloadTask", result + " bytes received: "); Log.d("PayloadTask", "doInBackground returning some payload from the aggboard."); // 传入实际接收长度,避免用整个数组长度导致解析脏数据 return Payload.CreatePayload(response, result);
内容的提问来源于stack exchange,提问作者user19409250
相关产品推荐
相关产品推荐

