0

0

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

聖光之護

聖光之護

发布时间:2025-08-14 22:48:17

|

620人浏览过

|

来源于php中文网

原创

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

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

Onu
Onu

将脚本转换为内部工具,不需要前端代码。

下载
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 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 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 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 中声明了必要的蓝牙权限:
    
    
    
    
    
     

    对于 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 广告通信。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
pdf怎么转换成xml格式
pdf怎么转换成xml格式

将 pdf 转换为 xml 的方法:1. 使用在线转换器;2. 使用桌面软件(如 adobe acrobat、itext);3. 使用命令行工具(如 pdftoxml)。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1897

2024.04.01

xml怎么变成word
xml怎么变成word

步骤:1. 导入 xml 文件;2. 选择 xml 结构;3. 映射 xml 元素到 word 元素;4. 生成 word 文档。提示:确保 xml 文件结构良好,并预览 word 文档以验证转换是否成功。想了解更多xml的相关内容,可以阅读本专题下面的文章。

2091

2024.08.01

xml是什么格式的文件
xml是什么格式的文件

xml是一种纯文本格式的文件。xml指的是可扩展标记语言,标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言。想了解更多相关的内容,可阅读本专题下面的相关文章。

1051

2024.11.28

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

395

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

575

2023.08.10

android开发三大框架
android开发三大框架

android开发三大框架是XUtil框架、volley框架、ImageLoader框架。本专题为大家提供android开发三大框架相关的各种文章、以及下载和课程。

282

2023.08.14

android是什么系统
android是什么系统

Android是一种功能强大、灵活可定制、应用丰富、多任务处理能力强、兼容性好、网络连接能力强的操作系统。本专题为大家提供android相关的文章、下载、课程内容,供大家免费下载体验。

1750

2023.08.22

android权限限制怎么解开
android权限限制怎么解开

android权限限制可以使用Root权限、第三方权限管理应用程序、ADB命令和Xposed框架解开。详细介绍:1、Root权限,通过获取Root权限,用户可以解锁所有权限,并对系统进行自定义和修改;2、第三方权限管理应用程序,用户可以轻松地控制和管理应用程序的权限;3、ADB命令,用户可以在设备上执行各种操作,包括解锁权限;4、Xposed框架,用户可以在不修改系统文件的情况下修改应用程序的行为和权限。

2033

2023.09.19

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

31

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
SQL 教程
SQL 教程

共61课时 | 3.6万人学习

10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

Midjourney 关键词系列整合
Midjourney 关键词系列整合

共13课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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