fix: 修复重连后 BLE write 失败问题 + 支持 composite build
- connect 前清理遗留 gatt,避免实例泄漏 - 等 CCCD descriptor 写入完成再 resume 连接,防止首次 write 卡在 gatt 串行队列 - writeWithoutResponse 失败后重试一次,仍失败抛 WriteFailed,让上层 RPC 快速失败而非等 10 秒超时 - sdk/build.gradle.kts 声明 group/version 以便 QA app composite build 引用 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,9 @@ plugins {
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
group = "com.duooomi"
|
||||
version = "0.0.0-local"
|
||||
|
||||
android {
|
||||
namespace = "com.duooomi.ble"
|
||||
compileSdk = 34
|
||||
|
||||
@@ -134,6 +134,16 @@ internal class BleClient(context: Context) {
|
||||
throw DuooomiBleError.BluetoothNotPoweredOn("Bluetooth is off")
|
||||
}
|
||||
|
||||
connectedGatt?.let { old ->
|
||||
BleLog.i("Stale gatt present; cleaning up before reconnect", "BLE")
|
||||
runCatching { old.disconnect() }
|
||||
runCatching { old.close() }
|
||||
connectedGatt = null
|
||||
connectedDeviceId = null
|
||||
writeCharacteristic = null
|
||||
readCharacteristic = null
|
||||
}
|
||||
|
||||
val device = bluetoothAdapter?.getRemoteDevice(deviceId)
|
||||
?: throw DuooomiBleError.ConnectionFailed("Device not found: $deviceId")
|
||||
|
||||
@@ -185,12 +195,17 @@ internal class BleClient(context: Context) {
|
||||
writeMutex.withLock {
|
||||
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
||||
characteristic.value = data
|
||||
val success = gatt.writeCharacteristic(characteristic)
|
||||
var success = gatt.writeCharacteristic(characteristic)
|
||||
if (!success) {
|
||||
BleLog.w("Write failed, len=${data.size}", "BLE")
|
||||
} else {
|
||||
BleLog.d("Write w/o response, len=${data.size}", "BLE")
|
||||
BleLog.w("Write failed; retry once after 30ms, len=${data.size}", "BLE")
|
||||
delay(30)
|
||||
success = gatt.writeCharacteristic(characteristic)
|
||||
}
|
||||
if (!success) {
|
||||
BleLog.e("Write failed after retry, len=${data.size}", "BLE")
|
||||
throw DuooomiBleError.WriteFailed("Gatt busy; write rejected")
|
||||
}
|
||||
BleLog.d("Write w/o response, len=${data.size}", "BLE")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,6 +309,9 @@ internal class BleClient(context: Context) {
|
||||
return
|
||||
}
|
||||
|
||||
connectedGatt = gatt
|
||||
connectedDeviceId = gatt.device.address
|
||||
|
||||
// Enable notifications on read characteristic
|
||||
val readChar = readCharacteristic!!
|
||||
gatt.setCharacteristicNotification(readChar, true)
|
||||
@@ -302,16 +320,32 @@ internal class BleClient(context: Context) {
|
||||
)
|
||||
if (descriptor != null) {
|
||||
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||
gatt.writeDescriptor(descriptor)
|
||||
val ok = gatt.writeDescriptor(descriptor)
|
||||
if (ok) {
|
||||
// Resume in onDescriptorWrite, after the CCCD write is flushed.
|
||||
return
|
||||
}
|
||||
BleLog.w("writeDescriptor returned false; resuming anyway", "BLE")
|
||||
}
|
||||
resumeConnect(gatt)
|
||||
}
|
||||
|
||||
// Connection fully ready
|
||||
connectedGatt = gatt
|
||||
connectedDeviceId = gatt.device.address
|
||||
val cont = connectContinuation
|
||||
override fun onDescriptorWrite(
|
||||
gatt: BluetoothGatt,
|
||||
descriptor: BluetoothGattDescriptor,
|
||||
status: Int
|
||||
) {
|
||||
BleLog.d("Descriptor written: uuid=${descriptor.uuid}, status=$status", "BLE")
|
||||
if (descriptor.uuid == java.util.UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")) {
|
||||
resumeConnect(gatt)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resumeConnect(gatt: BluetoothGatt) {
|
||||
val cont = connectContinuation ?: return
|
||||
connectContinuation = null
|
||||
BleLog.i("Characteristics ready; connection established", "BLE")
|
||||
cont?.resume(gatt)
|
||||
cont.resume(gatt)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
|
||||
@@ -24,6 +24,7 @@ sealed class DuooomiBleError(message: String) : Exception(message) {
|
||||
class DeleteFileFailed(val status: Int) : DuooomiBleError("Delete failed: status=$status")
|
||||
class PrepareTransferFailed(val status: String) : DuooomiBleError("Prepare failed: $status")
|
||||
class TransferFailed(reason: String) : DuooomiBleError("Transfer failed: $reason")
|
||||
class WriteFailed(reason: String) : DuooomiBleError("Write failed: $reason")
|
||||
class UnsupportedFormat(val format: String) : DuooomiBleError("Unsupported file format: $format. Supported: jpeg, png, gif, mp4, webm, webp")
|
||||
object InvalidFrame : DuooomiBleError("Invalid protocol frame")
|
||||
object PermissionDenied : DuooomiBleError("Bluetooth permission denied")
|
||||
|
||||
Reference in New Issue
Block a user