boxmoe_header_banner_img

Hello! 欢迎来到悠悠畅享网!

文章导读

Android BLE AdvertisingSet 扫描响应数据发送指南


avatar
站长 2025年8月15日 1

Android BLE AdvertisingSet 扫描响应数据发送指南

本教程详细阐述了在 Android BLE AdvertisingSet 中正确配置和发送扫描响应(Scan Response)数据的方法。核心在于确保 AdvertisingSetParameters 中设置 setScannable(true),以允许设备响应扫描请求并发送包含额外信息的扫描响应包。文章将通过代码示例和注意事项,指导开发者有效利用 AdvertisingSet 传输完整的 BLE 广告数据。

1. 理解 Android BLE 广告与扫描响应

低功耗蓝牙(ble)通信始于广告(advertising)过程。一个 ble 设备(advertiser)通过广播广告包(advertisement packet)来宣布其存在和提供的信息。这些广告包通常包含设备名称、服务 uuid 等基本信息。然而,广告包的大小是有限的(通常为 31 字节)。

为了传输更多数据,BLE 引入了扫描响应包(Scan Response Packet)的概念。当一个扫描器(Scanner)执行“主动扫描”(Active Scan)时,它会发送一个扫描请求(Scan Request)给正在广播的设备。如果该设备被配置为可响应扫描请求,它将发送一个扫描响应包,其中可以包含额外的设备信息,如制造商特定数据、更长的设备名称或服务数据等。

从 Android 8.0 (API 26) 开始,Google 引入了 AdvertisingSet API,提供了更灵活和强大的 BLE 广告能力,允许开发者同时管理多个广告集,并支持 LE Coded PHY 和 LE 2M PHY 等新特性。

2. 核心问题:扫描响应数据未发送的原因

在 AdvertisingSet 中,即使开发者通过 startAdvertisingSet 方法明确提供了 AdvertiseData 作为扫描响应包,扫描器端仍可能无法接收到这些数据。常见的问题表现为,扫描器接收到的 ScanResult 中,ScanRecord 对象的 mServiceData 或其他本应包含在扫描响应中的字段为空。

导致这一问题的根本原因在于,AdvertisingSetParameters 中缺少一个关键的配置:setScannable(true)。默认情况下,如果未明确设置,设备可能不会响应扫描请求,从而导致扫描响应数据无法发送。setScannable(true) 明确告诉 BLE 协议栈,该广告集是可被扫描的,并且应该在收到扫描请求时发送扫描响应。

3. 正确配置 AdvertisingSet 发送扫描响应

要确保 AdvertisingSet 能够成功发送扫描响应数据,需要正确配置 AdvertisingSetParameters、主广告数据和扫描响应数据,并启动广告集。

3.1 配置 AdvertisingSetParameters

AdvertisingSetParameters 定义了广告集的行为,包括是否可连接、是否使用传统模式、广告间隔和发射功率等。要发送扫描响应,最关键的一步是调用 setScannable(true)。

import android.bluetooth.le.AdvertisingSetParameters;  // ... (假设 serviceId 已经定义为 ParcelUuid)  AdvertisingSetParameters parameters = new AdvertisingSetParameters.Builder()         .setConnectable(false) // 根据需求设置是否可连接。此处设置为不可连接。         .setLegacyMode(true)   // 是否使用传统广告模式(ADV_IND, ADV_SCAN_IND等)。                                // 即使在传统模式下,setScannable(true) 也是发送扫描响应的关键。         .setScannable(true)    // 关键:允许设备响应扫描请求并发送扫描响应。         .setInterval(AdvertisingSetParameters.INTERVAL_HIGH) // 广告间隔,例如高频间隔。         .setTxPowerLevel(AdvertisingSetParameters.TX_POWER_MEDIUM) // 发射功率。         .build();

3.2 构建主广告数据 AdvertiseData

主广告数据是设备广播的基本信息,扫描器即使不发送扫描请求也能接收到。

import android.bluetooth.le.AdvertiseData; import android.os.ParcelUuid;  // ... (假设 serviceId 已经定义为 ParcelUuid)  AdvertiseData advertiseData = new AdvertiseData.Builder()         .setIncludeDeviceName(true) // 是否在主广告包中包含设备名称。         .setIncludeTxPowerLevel(false) // 是否在主广告包中包含发射功率。         .addServiceUuid(serviceId) // 添加服务 UUID。         .build();

3.3 构建扫描响应数据 AdvertiseData

扫描响应数据包含在主广告包中无法容纳的额外信息。这些数据只会在扫描器发送扫描请求后才被发送。

import java.nio.charset.StandardCharsets;  // ... (假设 serviceId 已经定义为 ParcelUuid)  byte[] packetContent = "hello".getBytes(StandardCharsets.UTF_8); // 示例:要发送的服务数据内容。  AdvertiseData scanResponseData = new AdvertiseData.Builder()         .setIncludeDeviceName(false) // 扫描响应中通常不需要再次包含设备名称,除非有特定需求。         .setIncludeTxPowerLevel(false) // 扫描响应中通常不需要再次包含发射功率。         .addServiceUuid(serviceId) // 再次添加服务 UUID,或添加其他服务 UUID。         .addServiceData(serviceId, packetContent) // 添加服务数据,这是本例中期望传输的关键信息。         .build();

3.4 启动广告集 startAdvertisingSet

最后,使用 BluetoothLeAdvertiser 实例启动广告集,传入之前构建的参数和数据。

import android.bluetooth.BluetoothAdapter; import android.bluetooth.le.BluetoothLeAdvertiser; import android.bluetooth.le.AdvertisingSetCallback; import android.bluetooth.le.AdvertisingSet; import android.util.Log;  // ... (假设 parameters, advertiseData, scanResponseData 已经定义) // ... (假设 serviceId 已经定义为 ParcelUuid)  BluetoothLeAdvertiser advertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();  if (advertiser == null) {     Log.e("BLEAdvertiser", "BluetoothLeAdvertiser not available.");     return; }  AdvertisingSetCallback advertisingSetCallback = new AdvertisingSetCallback() {     @Override     public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, int status) {         Log.d("BLEAdvertiser", "AdvertisingSet started, status: " + status);         // 可以在此处保存 advertisingSet 实例以便后续停止广告     }      @Override     public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) {         Log.d("BLEAdvertiser", "Advertising data set, status: " + status);     }      @Override     public void onScanResponseDataSet(AdvertisingSet advertisingSet, int status) {         Log.d("BLEAdvertiser", "Scan response data set, status: " + status);     }      @Override     public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {         Log.d("BLEAdvertiser", "AdvertisingSet stopped.");     }      @Override     public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, int status) {         Log.d("BLEAdvertiser", "Advertising enabled: " + enable + ", status: " + status);     }      // ... 其他回调方法,如 onAdvertisingSetStopped, onAdvertisingEnabled 等 };  // 启动广告集 advertiser.startAdvertisingSet(parameters, advertiseData, scanResponseData, null, null, advertisingSetCallback);

4. 验证:通过 BLE 扫描器接收扫描响应

为了验证扫描响应是否成功发送,需要配置一个 BLE 扫描器,并确保其执行的是主动扫描。

4.1 配置 ScanFilter 和 ScanSettings

扫描器需要设置 ScanSettings 以指定扫描模式。SCAN_MODE_LOW_LATENCY 是一个主动扫描模式,它会发送扫描请求并等待扫描响应。

import android.bluetooth.BluetoothAdapter; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.bluetooth.le.ScanRecord; import android.os.ParcelUuid; import android.util.Log; import java.util.ArrayList; import java.util.List; import java.util.Map;  // ... (假设 serviceId 已经定义为 ParcelUuid)  List<ScanFilter> filters = new ArrayList<>(); ScanFilter filter = new ScanFilter.Builder()         .setServiceUuid(serviceId) // 根据服务 UUID 过滤设备。         .build(); filters.add(filter);  // 设置扫描模式为低延迟,这通常意味着主动扫描。 ScanSettings scanSettings = new ScanSettings.Builder()         .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // 确保是主动扫描,以便接收扫描响应。         .build();

4.2 处理 ScanResult

在 ScanCallback 的 onScanResult 方法中,可以通过 ScanResult.getScanRecord() 获取 ScanRecord 对象,然后从中提取 ServiceData 来验证扫描响应是否成功接收。

// ... (假设 filters, scanSettings 已经定义) // ... (假设 serviceId 已经定义为 ParcelUuid)  BluetoothLeScanner bluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();  if (bluetoothLeScanner == null) {     Log.e("BLEScanner", "BluetoothLeScanner not available.");     return; }  ScanCallback scanCallback = new ScanCallback() {     private static final String TAG = "BLEScanner";      @Override     public void onScanResult(int callbackType, ScanResult result) {         super.onScanResult(callbackType, result);         ScanRecord scanRecord = result.getScanRecord();         if (scanRecord != null) {             // 打印完整的 ScanRecord 信息以便调试             Log.d(TAG, "ScanResult: " + result.toString());              // 检查 ServiceData 是否包含期望的内容             Map<ParcelUuid, byte[]> serviceData = scanRecord.getServiceData();             if (serviceData != null && serviceData.containsKey(serviceId)) {                 byte[] receivedData = serviceData.get(serviceId);                 String dataString = new String(receivedData, StandardCharsets.UTF_8);                 Log.d(TAG, "Received ServiceData for UUID " + serviceId.toString() + ": " + dataString);                 // 验证收到的数据是否为 "hello"                 if ("hello".equals(dataString)) {                     Log.d(TAG, "Scan response data received successfully!");                 }             } else {                 Log.d(TAG, "ServiceData for UUID " + serviceId.toString() + " not found in scan response.");             }         }     }      @Override     public void onBatchScanResults(List<ScanResult> results) {         super.onBatchScanResults(results);         // 处理批量扫描结果     }      @Override     public void onScanFailed(int errorCode) {         super.onScanFailed(errorCode);         Log.e(TAG, "Scan failed with error code: " + errorCode);     } };  // 启动扫描 bluetoothLeScanner.startScan(filters, scanSettings, scanCallback);  // 在适当的时候停止扫描 // bluetoothLeScanner.stopScan(scanCallback);

5. 注意事项与最佳实践

  • 主动扫描与被动扫描:再次强调,扫描响应只会在扫描器执行主动扫描时发送。被动扫描(如 SCAN_MODE_LOW_POWER 或 SCAN_MODE_BALANCED 的某些配置)只会接收主广告包。
  • legacyMode 的影响:即使在 AdvertisingSetParameters 中设置 setLegacyMode(true),表示使用传统广告格式,setScannable(true) 仍然是确保扫描响应发送的关键。AdvertisingSet API 提供了对传统广告模式的封装和更精细的控制。
  • 数据大小限制:无论是主广告包还是扫描响应包,其有效载荷大小都受到 BLE 规范的限制,通常为 31 字节。如果需要传输更多数据,可能需要考虑 GATT 服务或分包传输。
  • 权限:确保 AndroidManifest.xml 中声明了必要的蓝牙权限:
    <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- 对于扫描,尤其是在Android 6.0+ -->

    对于 Android 12 (API 31) 及更高版本,需要 BLUETOOTH_ADVERTISE, BLUETOOTH_SCAN, BLUETOOTH_CONNECT。对于 Android 11 (API 30) 及更低版本,通常需要 BLUETOOTH, BLUETOOTH_ADMIN 和 ACCESS_FINE_LOCATION。运行时权限请求也是必不可少的。

  • 错误处理与回调:在 AdvertisingSetCallback 中,务必实现 onAdvertisingSetStarted、onAdvertisingSetStopped 等回调方法,以便监控广告集的状态和处理可能发生的错误(如 status 参数非 ADVERTISE_SUCCESS)。

总结

通过本教程,我们深入探讨了 Android BLE AdvertisingSet 中扫描响应数据未发送的问题,并提供了详细的解决方案。核心要点在于,当使用 AdvertisingSet 并期望发送扫描响应数据时,必须在 AdvertisingSetParameters.Builder 中明确调用 setScannable(true)。同时,扫描器端也需要执行主动扫描才能请求并接收这些响应数据。遵循这些指导原则,开发者可以有效利用 AdvertisingSet 的强大功能,实现更丰富和灵活的 BLE 广告通信。



评论(已关闭)

评论已关闭