Android BLE扫描无结果:无法发现ESP32模块
Android BLE扫描无法发现ESP32模块的排查方案
问题背景
开发Android应用时,需与BLE ESP32模块建立连接并实现UTF-8消息收发,但低功耗扫描模式下无法发现目标模块。该模块可在nRF Connect中正常识别,应用无报错信息。以下为用户提供的蓝牙相关类代码:
lateinit var context: Context var bluetoothManager: BluetoothManager var bluetoothAdapter: BluetoothAdapter var bluetoothLeScanner: BluetoothLeScanner private var SERVICE_UUID: UUID = UUID.fromString("4fafc201-1fb5-459e-8fcc-c5c9c331914b") private var serviceUUIDs = arrayOf(SERVICE_UUID) var myDevices = HashMap<Int, BluetoothDevice>() var deviceCounter = 0 var scanning = false var handler = Handler() val TAGe: String = "JOST_TAG_ERROR:: " val TAGs: String = "JOST_TAG_SUCCESS:: " lateinit var scanFilters: List<ScanFilter> val scanSettings: ScanSettings = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE) .setNumOfMatches(ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT) .setReportDelay(0) .build() @RequiresApi(Build.VERSION_CODES.S) constructor(transmitted_context: Context){ this.context = transmitted_context bluetoothManager = transmitted_context.getSystemService(BluetoothManager::class.java) bluetoothAdapter = bluetoothManager.adapter bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner if(serviceUUIDs != null){ scanFilters = ArrayList<ScanFilter>() for(service: UUID in serviceUUIDs) { val filter = ScanFilter.Builder() .setServiceUuid(ParcelUuid(SERVICE_UUID)) .build() (scanFilters as ArrayList<ScanFilter>).add(filter) } } this.lookingfordevices() this.scanLeDevice() } var scanCallback: ScanCallback = object : ScanCallback() { @RequiresApi(Build.VERSION_CODES.S) override fun onScanResult(callbackType: Int, result: ScanResult) { Toast.makeText(context, "Starting onScanResult", Toast.LENGTH_LONG).show() var bluetoothGatt: BluetoothGatt? = null var device: BluetoothDevice = result.device as BluetoothDevice Toast.makeText(context, ""+result.device.uuids.toString(), Toast.LENGTH_LONG).show() myDevices[deviceCounter] = device deviceCounter++ if (ActivityCompat.checkSelfPermission( context, Manifest.permission.BLUETOOTH_CONNECT ) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions(context as Activity, arrayOf(Manifest.permission.BLUETOOTH_CONNECT), 1) } if ((result != null) && result.device.uuids.contentEquals(serviceUUIDs)) { Toast.makeText(context, "Scanresult isn't null and device UUID fits", Toast.LENGTH_LONG).show() bluetoothGatt = result.device.connectGatt(context, false, bluetoothGattCallback, BluetoothDevice.TRANSPORT_LE ) Toast.makeText(context, "Connecting ot Gatt: "+bluetoothGatt.device.address, Toast.LENGTH_LONG).show() } else { Log.e(TAGe, "THERE ARE NO DEVICES FOUND FROM YOUR SCAN RESULT") Toast.makeText(context, "There are no devices found!", Toast.LENGTH_LONG).show() } } override fun onScanFailed(errorCode: Int) { super.onScanFailed(errorCode) Toast.makeText(context, "onScan Failed!", Toast.LENGTH_LONG).show() } } private val bluetoothGattCallback = object: BluetoothGattCallback() { @RequiresApi(Build.VERSION_CODES.S) override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) { if(ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(context as Activity, arrayOf(Manifest.permission.BLUETOOTH_CONNECT),1) } if(status == BluetoothGatt.GATT_SUCCESS){ Toast.makeText(context, "GATT_SUCCESS", Toast.LENGTH_LONG).show() if(newState == BluetoothProfile.STATE_CONNECTED){ Toast.makeText(context, "GATT FINALLY CONNECTED", Toast.LENGTH_LONG).show() var bondstate = gatt?.device?.bondState if(bondstate == BluetoothDevice.BOND_NONE || bondstate == BluetoothDevice.BOND_BONDED){ gatt?.discoverServices() } else if (bondstate == BluetoothDevice.BOND_BONDING){ Toast.makeText(context, "Waiting Bonding to complete", Toast.LENGTH_LONG).show() } } else if (newState == BluetoothProfile.STATE_DISCONNECTED){ gatt?.close() } else { //TODO } } else { Toast.makeText(context, "NO SUCCES IN GATT", Toast.LENGTH_LONG).show() gatt?.close() } } } private val SCAN_PERIOD: Long = 10000 //stops scanning after 10 seconds fun scanLeDevice(){ Toast.makeText(context, "Scanning Devices", Toast.LENGTH_LONG).show() if(bluetoothAdapter.isEnabled){ Toast.makeText(context, "b_adapter_enabled", Toast.LENGTH_LONG).show() if(!scanning){ Toast.makeText(context, "Scanning was false", Toast.LENGTH_LONG).show() handler.postDelayed({ scanning = false if (ActivityCompat.checkSelfPermission( context, Manifest.permission.BLUETOOTH_SCAN ) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions( context as Activity, arrayOf(Manifest.permission.BLUETOOTH_SCAN), 1 ) } bluetoothLeScanner.stopScan(scanCallback) }, SCAN_PERIOD) scanning = true Toast.makeText(context, "Starting Scan therefore", Toast.LENGTH_LONG).show() bluetoothLeScanner.startScan(scanFilters,scanSettings,scanCallback) } else { Toast.makeText(context, "Scanned", Toast.LENGTH_LONG).show() scanning = false bluetoothLeScanner.stopScan(scanCallback) } } } fun lookingfordevices(){ CoroutineScope(Dispatchers.IO).launch { handler.postDelayed({ Log.d(TAGs, myDevices.size.toString()) scanLeDevice() lookingfordevices() },15000) } } @RequiresApi(Build.VERSION_CODES.S) fun checkinitialpermissionstate(){ if(!PermissionCheck().requestPermissionState(context)){ PermissionCheck().requestPermissions(context) } } }
核心排查与修复点
1. 扫描过滤器的匹配问题
- 代码中创建扫描过滤器时,循环遍历
serviceUUIDs但始终使用固定的SERVICE_UUID,属于冗余操作,但更关键的是:需确认ESP32广播的服务UUID与代码中指定的4fafc201-1fb5-459e-8fcc-c5c9c331914b完全一致。可通过nRF Connect查看ESP32的广播UUID,若不匹配,过滤器会直接过滤掉设备。 - 临时测试可移除扫描过滤器,调用
bluetoothLeScanner.startScan(null, scanSettings, scanCallback),验证是否能发现设备,以此确认过滤器是否为问题根源。
2. 低功耗扫描模式的漏扫问题
ScanSettings.SCAN_MODE_LOW_POWER扫描频率低,容易漏扫设备,而nRF Connect默认使用高功耗扫描,因此能正常发现。
- 测试阶段可先切换为
ScanSettings.SCAN_MODE_LOW_LATENCY,确认能发现设备后,再优化低功耗模式的参数(如调整MATCH_MODE和NUM_OF_MATCHES)。
3. 权限检查时机错误
scanLeDevice中延迟任务才检查BLUETOOTH_SCAN权限,但扫描已提前启动。权限检查必须在扫描操作前完成:
fun scanLeDevice(){ Toast.makeText(context, "Scanning Devices", Toast.LENGTH_LONG).show() if(bluetoothAdapter.isEnabled){ Toast.makeText(context, "b_adapter_enabled", Toast.LENGTH_LONG).show() if(!scanning){ // 扫描前先检查权限 if (ActivityCompat.checkSelfPermission( context, Manifest.permission.BLUETOOTH_SCAN ) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions( context as Activity, arrayOf(Manifest.permission.BLUETOOTH_SCAN), 1 ) return // 未获取权限,终止扫描流程 } Toast.makeText(context, "Scanning was false", Toast.LENGTH_LONG).show() handler.postDelayed({ scanning = false bluetoothLeScanner.stopScan(scanCallback) }, SCAN_PERIOD) scanning = true Toast.makeText(context, "Starting Scan therefore", Toast.LENGTH_LONG).show() bluetoothLeScanner.startScan(scanFilters,scanSettings,scanCallback) } else { Toast.makeText(context, "Scanned", Toast.LENGTH_LONG).show() scanning = false bluetoothLeScanner.stopScan(scanCallback) } } }
4. 重复扫描逻辑的冲突
lookingfordevices中使用协程配合Handler会导致重复调度,可能出现多次扫描叠加,干扰正常扫描。优化逻辑如下:
fun lookingfordevices(){ handler.postDelayed({ Log.d(TAGs, myDevices.size.toString()) // 仅当前未扫描时启动新扫描 if (!scanning) { scanLeDevice() } lookingfordevices() },15000) }
无需使用协程,Handler本身在主线程调度,协程+Handler会造成不必要的线程切换。
5. onScanResult中的UUID判断错误
扫描阶段result.device.uuids为null(该字段仅在设备连接后才会填充),原判断逻辑永远不成立,导致错误提示。需从扫描记录中获取广播的UUID:
override fun onScanResult(callbackType: Int, result: ScanResult) { Toast.makeText(context, "Starting onScanResult", Toast.LENGTH_LONG).show() var bluetoothGatt: BluetoothGatt? = null val device: BluetoothDevice = result.device Toast.makeText(context, device.address, Toast.LENGTH_LONG).show() myDevices[deviceCounter] = device deviceCounter++ // 权限检查提前 if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(context as Activity, arrayOf(Manifest.permission.BLUETOOTH_CONNECT), 1) return } // 从扫描记录中获取广播的服务UUID val scanRecord = result.scanRecord val advertisedUuids = scanRecord?.serviceUuids?.map { it.uuid } ?: emptyList() if (advertisedUuids.contains(SERVICE_UUID)) { Toast.makeText(context, "Scanresult matches service UUID", Toast.LENGTH_LONG).show() bluetoothGatt = device.connectGatt(context, false, bluetoothGattCallback, BluetoothDevice.TRANSPORT_LE) Toast.makeText(context, "Connecting to Gatt: "+bluetoothGatt.device.address, Toast.LENGTH_LONG).show() } else { Log.e(TAGe, "Device does not advertise target service UUID") Toast.makeText(context, "Device found but UUID does not match", Toast.LENGTH_LONG).show() } }
6. Android版本权限适配
确保AndroidManifest.xml中添加了对应版本的权限:
<!-- Android 11及以下 --> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" /> <!-- Android 12+ --> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
内容的提问来源于stack exchange,提问作者SeikoFPS
相关产品推荐
相关产品推荐

