BleScanner.kt 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package com.narutohuo.xindazhou.ble.util
  2. import android.bluetooth.BluetoothAdapter
  3. import android.bluetooth.BluetoothDevice
  4. import android.bluetooth.BluetoothManager
  5. import android.bluetooth.le.BluetoothLeScanner
  6. import android.bluetooth.le.ScanCallback
  7. import android.bluetooth.le.ScanFilter
  8. import android.bluetooth.le.ScanResult
  9. import android.bluetooth.le.ScanSettings
  10. import android.content.Context
  11. import android.os.Handler
  12. import android.os.Looper
  13. import com.narutohuo.xindazhou.core.log.ILog
  14. /**
  15. * BLE扫描器
  16. *
  17. * 负责 BLE 设备的扫描,返回符合过滤条件的 BluetoothDevice 列表
  18. * 使用 Android 12+ 推荐的 BluetoothLeScanner API
  19. */
  20. class BleScanner(
  21. private val context: Context,
  22. private val onDeviceFound: (BluetoothDevice) -> Unit,
  23. private val onScanFinished: () -> Unit,
  24. private val filterDeviceName: String? = null,
  25. private val filterDeviceAddress: String? = null
  26. ) {
  27. private val tag = "BleScanner"
  28. private val bluetoothAdapter: BluetoothAdapter by lazy {
  29. val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
  30. manager.adapter
  31. }
  32. private var scanner: BluetoothLeScanner? = null
  33. private var scanning = false
  34. private val scanCallback = object : ScanCallback() {
  35. override fun onScanResult(callbackType: Int, result: ScanResult) {
  36. val device = result.device
  37. if (device != null) {
  38. ILog.d(tag, "========== 发现设备 ==========")
  39. ILog.d(tag, "设备名称: ${device.name ?: "未知"}")
  40. ILog.d(tag, "设备地址: ${device.address}")
  41. ILog.d(tag, "RSSI: ${result.rssi}")
  42. ILog.d(tag, "扫描类型: $callbackType (1=CALLBACK_TYPE_ALL_MATCHES, 2=BATCH)")
  43. ILog.d(tag, "====================================")
  44. // 设备过滤(OR逻辑:名称匹配或地址匹配)
  45. val nameMatches = filterDeviceName?.let {
  46. device.name?.equals(it, ignoreCase = true) == true
  47. } ?: false
  48. val addressMatches = filterDeviceAddress?.let {
  49. device.address.equals(it, ignoreCase = true)
  50. } ?: false
  51. val shouldConnect = if (filterDeviceName != null || filterDeviceAddress != null) {
  52. // 如果设置了过滤条件,则必须匹配(OR逻辑)
  53. nameMatches || addressMatches
  54. } else {
  55. // 如果没有设置过滤条件,则连接所有设备
  56. true
  57. }
  58. if (shouldConnect) {
  59. ILog.d(tag, "✅ 设备匹配过滤条件(名称匹配=$nameMatches, 地址匹配=$addressMatches),准备连接")
  60. onDeviceFound(device)
  61. stopScan()
  62. } else {
  63. ILog.d(tag, "⏭️ 设备不匹配过滤条件,跳过")
  64. }
  65. }
  66. }
  67. override fun onBatchScanResults(results: List<ScanResult>) {
  68. ILog.d(tag, "批量扫描结果: ${results.size} 个设备")
  69. results.forEach {
  70. it.device?.let { device ->
  71. ILog.d(tag, "发现设备: ${device.name ?: "未知"}, 地址: ${device.address}, RSSI: ${it.rssi}")
  72. onDeviceFound(device)
  73. stopScan()
  74. }
  75. }
  76. }
  77. override fun onScanFailed(errorCode: Int) {
  78. ILog.e(tag, "========== 扫描失败 ==========")
  79. ILog.e(tag, "错误码: $errorCode")
  80. ILog.e(tag, "1=SCAN_FAILED_ALREADY_STARTED, 2=SCAN_FAILED_APPLICATION_REGISTRATION_FAILED, 3=SCAN_FAILED_INTERNAL_ERROR, 4=SCAN_FAILED_FEATURE_UNSUPPORTED, 5=SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES")
  81. ILog.e(tag, "====================================")
  82. stopScan()
  83. }
  84. }
  85. /**
  86. * 开始扫描(默认 10 秒后自动停止)
  87. */
  88. @android.annotation.SuppressLint("MissingPermission")
  89. fun startScan(filterNamePrefix: String? = null, timeoutMs: Long = 10_000L) {
  90. ILog.d(tag, "========== 开始扫描 ==========")
  91. ILog.d(tag, "过滤前缀: ${filterNamePrefix ?: "无"}")
  92. ILog.d(tag, "超时时间: ${timeoutMs}ms")
  93. ILog.d(tag, "蓝牙适配器状态: ${if (bluetoothAdapter.isEnabled) "已开启" else "已关闭"}")
  94. ILog.d(tag, "====================================")
  95. if (scanning) {
  96. ILog.w(tag, "⚠️ 扫描已在进行中,忽略重复请求")
  97. return
  98. }
  99. if (!bluetoothAdapter.isEnabled) {
  100. ILog.e(tag, "❌ 蓝牙未开启,无法扫描")
  101. return
  102. }
  103. scanner = bluetoothAdapter.bluetoothLeScanner
  104. if (scanner == null) {
  105. ILog.e(tag, "❌ BluetoothLeScanner 为 null")
  106. return
  107. }
  108. val settings = ScanSettings.Builder()
  109. .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
  110. .build()
  111. val filters = mutableListOf<ScanFilter>()
  112. filterNamePrefix?.let {
  113. filters.add(ScanFilter.Builder().setDeviceName(it).build())
  114. ILog.d(tag, "已设置设备名称过滤: $it")
  115. }
  116. try {
  117. scanner?.startScan(filters, settings, scanCallback)
  118. scanning = true
  119. ILog.d(tag, "✅ 扫描已启动,等待设备发现...")
  120. // 超时自动停止
  121. Handler(Looper.getMainLooper()).postDelayed({
  122. if (scanning) {
  123. ILog.w(tag, "⏰ 扫描超时 (${timeoutMs}ms),自动停止")
  124. stopScan()
  125. }
  126. }, timeoutMs)
  127. } catch (e: SecurityException) {
  128. ILog.e(tag, "❌ 扫描权限不足", e)
  129. scanning = false
  130. } catch (e: Exception) {
  131. ILog.e(tag, "❌ 启动扫描失败", e)
  132. scanning = false
  133. }
  134. }
  135. /**
  136. * 停止扫描
  137. */
  138. @android.annotation.SuppressLint("MissingPermission")
  139. fun stopScan() {
  140. if (!scanning) {
  141. ILog.d(tag, "stopScan() 被调用,但未在扫描中")
  142. return
  143. }
  144. ILog.d(tag, "停止扫描...")
  145. try {
  146. scanner?.stopScan(scanCallback)
  147. scanning = false
  148. ILog.d(tag, "停止扫描")
  149. onScanFinished()
  150. } catch (e: SecurityException) {
  151. ILog.e(tag, "停止扫描权限不足", e)
  152. } catch (e: Exception) {
  153. ILog.e(tag, "停止扫描失败", e)
  154. }
  155. }
  156. }