
低功耗蓝牙(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 等新特性。
在 AdvertisingSet 中,即使开发者通过 startAdvertisingSet 方法明确提供了 AdvertiseData 作为扫描响应包,扫描器端仍可能无法接收到这些数据。常见的问题表现为,扫描器接收到的 ScanResult 中,ScanRecord 对象的 mServiceData 或其他本应包含在扫描响应中的字段为空。
导致这一问题的根本原因在于,AdvertisingSetParameters 中缺少一个关键的配置:setScannable(true)。默认情况下,如果未明确设置,设备可能不会响应扫描请求,从而导致扫描响应数据无法发送。setScannable(true) 明确告诉 BLE 协议栈,该广告集是可被扫描的,并且应该在收到扫描请求时发送扫描响应。
要确保 AdvertisingSet 能够成功发送扫描响应数据,需要正确配置 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();主广告数据是设备广播的基本信息,扫描器即使不发送扫描请求也能接收到。
import android.bluetooth.le.AdvertiseData;
import android.os.ParcelUuid;
// ... (假设 serviceId 已经定义为 ParcelUuid)
AdvertiseData advertiseData = new AdvertiseData.Builder()
.setIncludeDeviceName(true) // 是否在主广告包中包含设备名称。
.setIncludeTxPowerLevel(false) // 是否在主广告包中包含发射功率。
.addServiceUuid(serviceId) // 添加服务 UUID。
.build();扫描响应数据包含在主广告包中无法容纳的额外信息。这些数据只会在扫描器发送扫描请求后才被发送。
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();最后,使用 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);为了验证扫描响应是否成功发送,需要配置一个 BLE 扫描器,并确保其执行的是主动扫描。
扫描器需要设置 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();在 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);<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。运行时权限请求也是必不可少的。
通过本教程,我们深入探讨了 Android BLE AdvertisingSet 中扫描响应数据未发送的问题,并提供了详细的解决方案。核心要点在于,当使用 AdvertisingSet 并期望发送扫描响应数据时,必须在 AdvertisingSetParameters.Builder 中明确调用 setScannable(true)。同时,扫描器端也需要执行主动扫描才能请求并接收这些响应数据。遵循这些指导原则,开发者可以有效利用 AdvertisingSet 的强大功能,实现更丰富和灵活的 BLE 广告通信。
以上就是Android BLE AdvertisingSet 扫描响应数据发送指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号