首页 > Java > java教程 > 正文

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

聖光之護
发布: 2025-08-14 22:48:17
原创
599人浏览过

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

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

Alkaid.art
Alkaid.art

专门为Phtoshop打造的AIGC绘画插件

Alkaid.art 153
查看详情 Alkaid.art
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 广告通信。

以上就是Android BLE AdvertisingSet 扫描响应数据发送指南的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号