拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

BLE设备MAC地址为何总变?四种地址类型与隐私保护详解

BLE设备MAC地址为何总变?四种地址类型与隐私保护详解

写在前面的一个疑问:为什么你的BLE设备MAC地址总在变?

做BLE开发这么多年,我见过太多人在设备识别上栽跟头。最常见的一个场景:你用手机扫描周围蓝牙设备,明明同一个硬件,今天看到的MAC地址是AA:BB:CC:DD:EE:FF,明天再看变成了12:34:56:78:9A:BC,甚至在同一次扫描里出现两次、两个不同地址。很多人第一反应是“设备坏了”或者“是不是有假设备”。

其实不是。这个现象背后,就是BLE协议栈里那套看似简单、实则容易被忽视的地址管理机制在起作用。搞清楚Public Device Address、Random Static Address、Resolvable Private Address、Non-resolvable Private Address这几种地址类型,你会发现很多“玄学”问题都变得非常有逻辑。这篇东西不是抄协议栈文档,而是把我自己在实际调试中踩过的坑、总结的判断方法,以及从抓包到代码实现的完整链路都拿出来聊聊。不管你是刚入门BLE、正在做固件开发,还是被iOS/Android端设备识别问题折磨的应用层开发,这篇都值得看完。

1. 为什么BLE里会有这么多种MAC地址

1.1 传统蓝牙与BLE在地址管理上的分水岭

传统蓝牙(BR/EDR)时代,设备的MAC地址基本就是固定那一个,是出厂固化下来的,应用层和协议栈都拿它当设备的唯一标识。那个年代,你搜索到一个蓝牙设备,它的地址就是它的“身份证”,几乎不会变。

到了BLE(Bluetooth Low Energy)时代,情况彻底变了。BLE的设计目标之一是低功耗、低复杂度,但它同时还要解决一个传统蓝牙不太在意的问题:隐私。想象一下,你戴着一个BLE手环,它不停地在广播,如果广播包里永远带着同一个固定MAC地址,那么任何人在商场里拿手机一扫,就能记录下这条路径:“这个地址的设备周一到周五每天早上8点出现在地铁站,下午6点出现在某小区门口”——这完全是个人行踪的泄露。

所以BLE规范引入了一套更灵活的地址体系,把地址拆成两大类:Public Device Address(公共设备地址)和Random Device Address(随机设备地址)。其中随机设备地址又细分为三种:Static(静态)、Private Resolvable(可解析私密)、Private Non-resolvable(不可解析私密)。总共四种类型,才是完整的BLE地址族。

1.2 一个核心认知:BLE地址不等于“设备ID”

理解这套机制,最重要的一个认知转变是:BLE里面的MAC地址,不完全等同于设备的唯一身份标识。

传统以太网MAC地址的语义是“这块网卡是谁”,而BLE地址的语义更复杂。Public地址和Static随机地址可以承载“身份”的语义;而Private地址(无论可解析还是不可解析)本质上是一个临时身份,它们存在的意义就是让外部观察者无法长期稳定地关联到某个设备。甚至可以说,Private地址是“故意模糊身份”的,对不对得上号,取决于你是否拥有解析它的密钥。

所以做BLE开发,第一步就是分清:你拿到的这个地址,到底能不能当唯一标识用?很多应用层开发者直接把扫描到的device.getAddress()存进数据库当主键,结果地址一变,整个用户体系全乱了。正确做法是:先判断地址类型,再决定要不要用它做持久化标识。后面我会讲怎么判断。

还有个容易忽略的点,不同芯片平台对地址的处理方式还有差异。比如Nordic nRF52840、ESP32、TI CC2640,它们在协议栈层面对地址类型的暴露方式并不完全一致,有的默认用Static Random Address,有的用Public Address,有的支持RPA但需要你手动开启。这也是为什么同样一套上层逻辑,换了一块芯片就出现“地址看不到了”或者“设备连不上”这种诡异问题。

2. Public Device Address:设备出厂自带的固定“身份证”

2.1 地址格式:从IEEE OUI说起

Public Device Address,翻译过来就是公共设备地址,它最大的特点就是全球唯一、出厂固化。这个地址的格式和传统以太网MAC地址完全一致,48位(6字节),分两部分:

  • 前24位是Company ID(公司标识),也叫OUI(Organizationally Unique Identifier),由IEEE统一分配。比如你看到00:0C:29这个前缀,大概率是VMware虚拟机的网卡,AC:67:B2是某款路由器常见厂商。在BLE设备里,OUI同样能定位到芯片原厂或模组厂商。
  • 后24位是Network Interface Controller(NIC)特定部分,由厂商自己分配,用来区分同一厂商出厂的不同设备。

举一个具体例子,假设某BLE模组的MAC地址是F0:08:D1:8A:3C:21,其中F0:08:D1就是IEEE分配给该模组芯片厂商的OUI,8A:3C:21是厂商内部流水号。两者拼接起来,理论上是全球唯一的。

2.2 谁在使用Public Address

Public Device Address在BLE世界里用得反而没有想象中那么多。原因是它太“暴露”了。

想想看,如果一个BLE设备用的是Public Address,那么它在广播、扫描响应、连接请求里的地址都是这个固定值。任何第三方只需在附近抓包,就能拿到这个地址。然后他可以拿着这个地址去IEEE的OUI数据库反查厂商,再配合信号出现的位置和时间,轻松绘制出设备持有人的活动轨迹。对很多消费级设备来说,这是隐私灾难。

所以实际产品中,Public Device Address常见于两类场景:一类是工业设备、医疗设备、传感器节点等对隐私要求不高的场景,这类设备部署位置固定,地址稳定反而是优势;另一类是那些需要通过MAC地址做白名单过滤、设备准入控制的场景,固定地址方便做硬件级的访问管理。

如果你在做自己的BLE产品原型,想快速验证功能,直接用Public Address也完全可以。大多数开发板出厂默认就是Public Address,省事。

2.3 Public Address带来的坑:OUI查询与虚拟机联想

关于Public Address有个挺有意思的细节。很多人在局域网里扫到00:0C:29开头的MAC地址,第一反应是“这不是VMware虚拟机吗?”确实,00:0C:29是VMware的OUI,说明这块虚拟网卡是VMware生成的。但这跟BLE有什么关系?关系在于:如果你在一个BLE调试环境里跑虚拟机,然后虚拟机里的软件通过虚拟蓝牙适配器去扫描设备,扫到的地址和真实硬件地址可能会让你迷惑。

我自己就干过这事。在VMware里跑Ubuntu,装了hcitool去lescan,看到一堆地址带00:0C:29前缀的“BLE设备”飘在列表里。排查了半天才发现,那根本不是物理BLE设备,而是虚拟机的虚拟蓝牙控制器在广播自己的地址。所以,用虚拟机做BLE开发调试,地址前缀的判断逻辑要做特殊处理,否则很容易把虚拟设备当成真实设备,或者反过来漏掉真实设备。

还有个更隐蔽的坑:部分低功耗蓝牙芯片(比如某些国产BLE SoC)出厂时烧录的Public Address,并不一定向IEEE申请了正规OUI——有些厂商直接用自己随意编造的前缀,甚至所有芯片共用同一个前缀、只有后24位不同。这种情况下,如果你依赖“OUI识别厂商”这个逻辑去做兼容判断,就会被带偏。

3. Random Device Address:随机地址家族的三个分支

3.1 Random Static Address:静态随机地址,看似随机实则固定

Random Static Address(随机静态地址),名字里有“Random”也有“Static”,这两个词放一起本身就值得琢磨。它的生成规则是:48位地址里,最高两个bit(第47和第46位)必须是11,剩下的46位是随机生成。这个地址在设备每次上电时生成一次,之后在本次生命周期内保持不变,直到下一次重新上电——所以叫“静态”。

为什么说“看似随机实则固定”?因为从外部观察者的角度看,这个地址像Public Address一样稳定,可以跟踪、可以记录;但它本身又是随机生成的,跟硬件出厂序列号没关系,不能通过它反推厂商或设备批次。换句话说,它是“一次性生成的固定身份”,兼顾了稳定性和一定的隐私性。

在BLE产品中,Random Static Address是最常用的一种地址类型。很多模组出厂固件默认使用的就是它,原因是它不需要向IEEE申请OUI,厂商可以在固件里直接用随机数生成器产生一个地址,省去了MAC地址采购成本。而且对大多数应用来说,它的“设备生命周期内稳定”这个特性已经足够支撑设备管理、白名单、绑定等常见功能。

3.2 Private Address:私密地址的诞生逻辑

Private Address(私密地址)是BLE针对隐私保护设计的核心机制,它又细分为Resolvable Private Address(可解析私密地址,RPA)和Non-resolvable Private Address(不可解析私密地址,NRPA)。

要理解Private Address,需要先理解它要解决的痛点:一个BLE设备如果长期使用固定地址广播,就相当于把行踪写在额头上,谁都能跟踪。而如果每次广播都换一个新地址,又带来了另一个问题——对端设备怎么认出“你”是谁?总不能每次都需要用户手动重新配对。

私密地址的解法很巧妙:地址可以变,但变的规律只有“自己人”知道。它通过一个分发出去的密钥(Identity Resolving Key,IRK),让持有同样密钥的设备能够从不断变化的地址里“解析”出设备真实身份。这个过程叫做Resolvable Private Address Resolution,后面我会详细拆解它的位数分配和算法流程。

3.3 一个必须记住的地址位判断表

在实际开发中,怎么判断一个地址属于哪一类?不需要翻协议栈源码,只需要看48位地址的最高两个bit(MSB),也就是第一个字节的最高两位。

地址类型最高两位bit值(bit47, bit46)第二位bit(bit45)含义典型用途
Public Device Address00固定出厂固化的全球唯一地址
Random Static Address11随机上电生成、生命周期内固定
Resolvable Private Address10随机可被持有IRK的对端解析
Non-resolvable Private Address01随机不可解析,纯临时

用十六进制表述更直观:第一个字节最高两位是00就是Public,01是NRPA,10是RPA,11是Static。比如扫描到一个设备的MAC地址是D6:3B:2A:00:11:22,首个字节D6换算成二进制是1101 0110,最高两位是11,那它就是Random Static Address。

这张表建议截图保存。我实际开发中判断地址类型,90%的场合靠它就是够了。

3.4 认识地址类型前先避开“高位bit”误区

关于地址位判断,有一个非常容易犯的错:很多初学者会去看“第一个字节的最后一个bit”,也就是bit0,那是单播/多播标志位,跟地址类型没关系。判断地址类型永远看bit47和bit46,也就是第一个字节的最高两位。在代码里,这个判断通常写成addr[0] >> 6,拿到的值就是0、1、2、3,分别对应Public、NRPA、RPA、Static。

还有一个点:不要把随机地址的“随机”理解为“每次广播都变”。只有Private Address才是会周期性变化的,Random Static Address在设备生命周期内是稳定的。不少人在测试时发现设备广播地址变了,就以为芯片的Static Address在漂移——大概率是没搞清楚,芯片实际用的是RPA或NRPA模式。

4. Resolvable Private Address:BLE隐私保护的“加密暗号”

4.1 RPA的地址结构:三个组成部分

Resolvable Private Address(RPA)是整个BLE地址体系里最精巧、也最需要理解的部分。一个48位的RPA地址,内部其实是分段的:

  • 最高两位(bit47~46)固定为10,用于标识这是RPA。
  • 中间24位(bit45~22)是prand(随机数部分),每次生成地址时随机产生。
  • 最低22位(bit21~0)是hash(哈希值),由IRK和prand经过加密算法计算得出。

也就是说,RPA地址 =10+prand(24位)+hash(22位)。整个地址的生成过程和解析过程,都围绕这个结构展开。

为什么要这个结构?因为它解决了一个安全问题:如果私密地址纯粹是随机数,那么持有密钥的对端设备也无法识别“这是不是来自那个设备”的广播。有了这个hash段,持有IRK的设备就能通过同样的算法,验证某个地址是否由特定IRK生成——这个验证过程就叫地址解析。

4.2 RPA生成过程:从IRK到新地址

RPA的生成算法在Bluetooth Core Spec Vol 6 Part B中有详细定义,用到了AES-128加密。我这里用大白话讲一遍流程:

  1. 设备内部保存一个128位的IRK密钥,这个密钥在配对时通过SMP(Security Manager Protocol)分发给对端设备。
  2. 生成一个24位的随机数prand,注意这个随机数的最高两位必须满足RPA地址的标志位要求。
  3. 用一个保留位填充(通常是0),与prand组成一个128位的数据块。
  4. 用IRK作为AES-128的密钥,对这个数据块进行加密。
  5. 从加密结果中取出最低24位,与prand进行拼接。
  6. 拼接后的48位地址,把最高两位强制设为10,得到一个完整的RPA地址。

这个地址生成后并不是永久有效的。按照Core Spec的推荐,RPA应该在一个定时周期后重新生成,常见实现是15分钟到1小时不等。Nordic的协议栈里有一个RPA_TIMEOUT参数可以配置,默认是APP_ADV_RPA_TIMEOUT。

我画一个简单的伪代码逻辑帮助理解:

// RPA生成示意(非协议栈源码,仅用于理解算法流程) uint8_t prand[3]; // 24位随机数 uint8_t hash[3]; // 22位hash结果 generateRandom(prand, 3); prand[2] &= 0x3F; // 保证最高两位为00,后续拼接时再置为10 // 用IRK对prand进行AES加密 uint8_t encrypted[16]; aes128_encrypt(irk, input, encrypted); // 取加密结果低24位作为hash memcpy(hash, encrypted + 13, 3); hash[2] &= 0x3F; // 只保留22位 hash[2] |= 0x40; // 将地址最高两位置为10,标识为RPA // 完整地址 = prand + hash

注意这里有个细节:RPA的最高两位必须是10,但prand本身在用IRK加密前,它的最高两位其实不被要求是什么特定值,只是在最终拼出48位地址时,要确保bit47和bit46是10。每个协议栈的具体填充方式略有差异,但最终结果一定满足判断表里的特征。

4.3 RPA解析过程:对端设备如何识别“自己人”

RPA的解析过程恰好是生成的逆操作。当一个设备收到一个RPA地址时,它手里可能保存着多个已配对设备的IRK。它会这样处理:

  1. 从收到的48位地址中,取出prand部分(中间24位)。
  2. 将prand用同样的方式填充成128位数据块。
  3. 用自己保存的某个IRK对该数据块进行AES加密。
  4. 取结果低24位,和收到的地址里hash部分做比对。
  5. 如果一致,说明这个地址就是由该IRK生成的,设备身份确认;如果不一致,换下一个IRK再试。

这个过程就是Resolution(解析)。如果设备没有保存任何IRK,或者所有IRK都试过后没有匹配上,那么这个RPA就无法被解析,对端只能认为这是一个未知设备。

这里有个很重要的工程概念:IRK标识设备身份,RPA只是它的“变色外衣”。在应用层,你需要维护一个“IRK -> 设备信息”的映射表,每当扫描到一个新的RPA地址时,通过协议栈的解析功能拿到对应的IRK,再通过IRK找到你的设备记录。iOS的CoreBluetooth和Android的BluetoothLeScanner,在系统层面已经封装好了这套解析逻辑,但不同平台暴露的行为不一样,后面我会细说。

4.4 在协议栈里配置RPA需要注意什么

实际开发中,RPA的坑比理论更多。以我常用的Nordic nRF52840为例:

  • 默认情况下,SDK里很多示例工程使用Static Random Address,而不是RPA。如果你想开启RPA,需要在ble_gap_evt_t和ble_gap_conn_params_t的GAP参数里配置,并且注册Identity Address。
  • 配了RPA之后,连接参数里的地址和白名单逻辑也要跟着调整。比如你原来用白名单过滤设备,用的是设备地址列表,现在设备地址是动态的,白名单条目就要改成IRK列表,或者配合Identity Address。
  • 还有一个容易忽略的点:设备在广播阶段的RPA和连接成功后的Identity Address不一定相同。Identity Address才是设备的稳定标识,RPA只是广播阶段的外衣。在应用层做持久化存储时,存的一定是Identity Address,不是每次广播的RPA。

ESP32的情况类似。ESP32的BLE默认地址类型可以通过esp_ble_gap_set_rand_addr()设置,同时它有esp_ble_resolve_adv_data()这类接口,但真正做RPA解析,需要在控制器和主机协议栈之间匹配正确的IRK管理。实测中,如果你不主动配置IRK,只用默认的“Static Random Address”,那么地址虽然是随机的,但解析不了,因为压根没有RPA机制在运行。

5. Non-resolvable Private Address:真正的“一次性马甲”

5.1 生成规则与适用场景

Non-resolvable Private Address(NRPA),不可解析私密地址,是四种地址里最“随性”的一种。它的结构非常简单:最高两位是01,剩下的46位全部随机生成。每次生成一个NRPA,就产生一个全新的、与任何密钥无关的地址,而且没有任何办法能从地址本身推算出设备身份——因为它压根不含任何可验证的身份信息。

NRPA的典型应用场景是防跟踪广播。比如一个设备在某个特殊场合希望完全隐藏自己的身份,不希望任何对端有能力识别“这还是之前那个设备”,它就可以每次广播前重新生成一个NRPA。从外部观察者的角度看,每次看到的都是一个新设备。

在BLE的Beacon应用里,NRPA也有应用。比如某些防丢器,在配对之前用NRPA广播,这样任何路过的人无法把某个地址和某个实体设备关联起来。还有一些低功耗传感器,上报数据时也不关心对端是否认识自己,直接随机地址发包,简单粗暴。

5.2 为什么NRPA不能用于可连接设备

理论上,NRPA也是可以用于连接请求的,但实际产品里你几乎不会看到它用于可连接广播。原因很现实:连接的后续流程需要身份信息。

BLE连接建立之后,要进行配对(Pairing)、密钥分发(Key Distribution)等安全流程。这些流程里,双方需要交换并确认对方的身份。如果连接请求用的地址是NRPA,且后续没有可解析的身份信息,那么双方都无法确定对方是谁——这会造成严重的安全漏洞。比如中间人攻击,攻击者冒用一个NRPA地址,对端根本无法分辨。

Core Spec的指南也基本明确:可连接广播(Connectable Advertisement)应该使用Public Address、Static Random Address或Resolvable Private Address,NRPA主要用于不可连接的广播(Non-connectable Advertisement),比如纯Beacon广播。

5.3 RPA和NRPA的对比,一张表说清

把RPA和NRPA放在一起对比,你会发现它们设计目标完全不同:

对比项Resolvable Private Address(RPA)Non-resolvable Private Address(NRPA)
最高两位1001
是否包含身份信息是,可由IRK解析出设备身份否,完全不含身份信息
地址是否变化周期性变化(典型15分钟~1小时)可每次广播都变化
是否需要密钥需要IRK,密钥在对端分发不需要任何密钥
能否被跟踪持有IRK的设备可识别,外部设备无法跟踪谁都无法识别,对端也不能识别
典型用途可连接广播、需要隐私保护的配对设备不可连接广播、Beacon、防跟踪场景

我一直把RPA比作“对上暗号才认识你的人”,把NRPA比作“戴了全新面具的陌生人”。前者有迹可循,后者无迹可查,各有各的适用场景。

5.4 一个容易被忽略的点:NRPA的碰撞概率

NRPA是46位随机,碰撞概率理论上非常低,但不是零。如果某个环境里有大量使用NRPA的设备同时广播,理论上可能出现两个设备生成同一个NRPA的概率——这个概率大约是2的46次方分之一,日常场景基本可以忽略。但在做大规模设备管理平台时,如果你用了NRPA地址做临时会话标识,还是建议加上额外的设备特征(比如广播数据里的Service UUID、Manufacturer Data)来做联合判断,避免极小概率下的会话串扰。

实话说,NRPA在我做过的项目里用得最少,因为它太“无迹可寻”了,管理和调试都不方便。但它确实填补了RPA解决不了的场景:RPA需要预先分发密钥,NRPA不需要任何前置信任关系。

6. 实际开发中如何判断和应对MAC地址变化

6.1 应用层必须做的事:判断地址类型,再决定存储策略

对上层应用开发者来说,最痛苦的问题永远是:“我到底能不能拿MAC地址当设备ID?”

先说结论:Public Address和Random Static Address可以用来做基础的设备区分;RPA在解析后可以用Identity Address做设备区分;NRPA绝对不能用于设备识别。

在iOS上,情况特殊。从iOS 13开始,系统在蓝牙扫描结果里返回的地址基本上都被加工过了,很多情况下你拿到的是一个UUID而非真实MAC地址。这是苹果刻意为之的隐私策略。CoreBluetooth框架里,CBPeripheral.identifier是一个UUID,由系统根据设备地址和某些内部逻辑生成,但同一个设备,在你没有配对前和配对后,这个UUID可能都会变化。所以苹果生态里,不要依赖MAC地址做设备持久化,要靠配对和本地缓存机制。

Android相对好一点。BluetoothDevice.getAddress()能拿到字符串形式的MAC地址,从Android 6.0开始需要ACCESS_COARSE_LOCATION或ACCESS_FINE_LOCATION权限,Android 12开始进一步限制了对MAC地址的访问,部分设备上非系统应用拿到的是一个随机化的地址。如果你在Android上做BLE外设开发,需要清楚这些限制,在AndroidManifest.xml里申请权限,并且在运行时处理用户的定位授权。

6.2 实用抓包方法:从空中报文反推地址类型

如果做固件或者协议栈调试,强烈推荐用抓包器直接看空中的报文。我常用的是nRF52840 Dongle配合Wireshark的btle插件,或者用Telink的BQB认证工具。抓包时,重点看广播报文里AdvA字段,这个字段就是广播设备的地址。

在Wireshark里,AdvA字段会直接标注地址类型,比如Public、Random、Resolvable、Non-resolvable。如果你想验证自己的判断逻辑,可以用一个简单方法:

  1. 找到广播报文里的AdvA,复制这份48位地址。
  2. 用计算器或Python把地址第一个字节转成二进制。
  3. 看二进制最高两位,对照前面那张表。

比如抓到一个地址6A:5B:1C:2D:3E:4F,第一个字节0x6A = 0110 1010,最高两位是01,那它就是NRPA。这招实测最准,比看协议栈日志直接得多。

6.3 Wireshark过滤表达式与日志排查建议

用Wireshark做BLE抓包时,一些好用的过滤表达式能大幅提升效率:

# 只看广播报文 btle.advertising_address # 按具体MAC地址过滤 btle.advertising_address == 6a:5b:1c:2d:3e:4f # 追踪一个固定设备的所有报文(包括连接后) bluetooth.addr == 6a:5b:1c:2d:3e:4f # 筛选扫描请求 btle.scan_request

如果你在排查“为什么设备连不上”的问题,建议同时开BLE抓包和串口日志,时间戳对齐后进行分析。很多协议栈的日志里会打印RPA expired、resolution failed之类的信息,这些关键词直接指向IRK配置或地址刷新问题。

另外说一个我踩过的坑:有些芯片在开启RPA模式后,连接参数里配置的白名单如果不重新声明为“使用IRK列表”,连接会一直失败。因为白名单匹配的是地址,但地址每分钟都在变。排查时如果发现设备在广播、手机也能搜到,但一发起连接就立刻断开,优先级最高的怀疑对象就是“白名单里没有匹配到动态地址对应的IRK”。

6.4 高隐私模式与系统限制的兼容方案

为了兼顾隐私和业务需求,现在很多BLE产品采用**“未配对时用RPA广播 + 配对后保存Identity Address”**的模式,这也是Core Spec比较推荐的架构。

以手环类产品为例:

  1. 出厂阶段:设备使用RPA进行可连接广播,等待手机扫描。
  2. 首次配对:手机App扫描到设备,发起连接,双方通过SMP配对,交换IRK。
  3. 配对完成后:设备保存手机IRK,手机保存设备IRK。
  4. 后续连接:设备再次以RPA广播,手机通过解析RPA识别出“这是已配对设备”,自动重连,无需用户再次选择。

这套方案里,设备在公开场合广播的永远是RPA,外部无法跟踪,而真正已配对的手机却能准确认出它。这就是RPA的核心价值——对外隐藏,对内透明。

如果你的产品不需要这么强的隐私保护,用Static Random Address就足够了。它虽然不能防止跟踪,但至少避免了你需要为每个设备申请IEEE OUI的成本。很多便宜的BLE模组出厂就是Static Random Address,默认就是这个原因。

7. 常见问题与排查技巧实录

7.1 设备MAC地址为什么会改变

这是被问得最多的一个问题。概括来说,地址变化只有三个原因:

  • 设备用了RPA,地址按定时器周期刷新。解决:去协议栈配置里找RPA timeout,或者改用Static Random Address。
  • 设备用了NRPA,每次广播都可能换地址。解决:改成Static Random Address或RPA。
  • 手机端(特别是iOS)对扫描结果做了抽象,你看到的不是设备的真实地址。解决:用抓包器验证空中报文,或者用Android原生机型做对照测试。

还有个容易忽略的方向:有些芯片在“上电重新初始化BLE协议栈”时,如果随机数种子是临时生成的,Static Random Address也会在每次上电后变化。解决这个问题,需要把随机地址存放在非易失存储区(Flash/NVM),上电时先读取再设置。我遇到过某国产芯片就是这种设计,第一次上电生成一个Static Address,断电重启后又变了,排查了很久才发现是Flash里没有保存地址。

7.2 一个实战排查案例:iOS上为什么搜不到设备了

有一次,我用nRF52840做外设,固件里开了RPA模式,手机用iOS系统自带蓝牙列表扫描,前几次能搜到,后面突然就搜不到了。而用Android手机测试,一直正常。

排查过程是这样的:

  1. 用抓包器看空中报文,确认外设一直在正常广播,地址是RPA,且周期变化正常。
  2. 检查iOS端日志,发现系统在尝试解析RPA失败后,直接把这个外设过滤掉了。
  3. 进一步检查,发现iOS并没有保存这个外设的IRK。原因是我在测试时,虽然之前成功配对过一次,但iOS端App删除绑定时没有正确清除系统保存的配对信息,导致系统层面用了一个旧IRK或者没有IRK去解析新的RPA。
  4. 解决办法:在iOS设置里还原蓝牙相关配对记录,重新配对,问题解决。

这个案例给我们的教训是:RPA依赖系统端的IRK保存,App层删除绑定记录时,必须同步清除系统Bluetooth配对缓存。在iOS上,清除方式通常是调用CBPeripheralManager的remove相关方法,或者引导用户在系统蓝牙设置里“忽略此设备”。

7.3 关于“应用获取MAC地址的方法”和权限限制

隔一段时间就会有人问我:“我的App怎么获取不到蓝牙设备的MAC地址了?”

这个问题的答案跟Android系统版本强相关:

  • Android 6.0(API 23)及以上,需要ACCESS_COARSE_LOCATION或ACCESS_FINE_LOCATION权限才能扫描BLE设备。
  • Android 12(API 31)及以上,MAC地址信息进一步受限,getAddress()在某些非系统应用里返回的是02:00:00:00:00:00这样的空值。
  • 如果应用的目标SDK版本很高(比如targetSdk 31+),BLUETOOTH_SCAN权限也是必需的,并且还要处理运行时权限弹窗。

解决思路:不要把设备识别完全寄托在MAC地址上。BLE设备在广播数据里可以携带自定义的Service UUID、Device Name、Manufacturer Specific Data等内容,这些字段的组合完全可以作为设备唯一标识。比如Nordic的DFU Service、Apple的iBeacon数据,都用广播数据里的字段来区分设备,而不是依赖MAC地址。

7.4 排查清单:地址相关问题的快速自查表

总结一个我一直在用的自查清单,遇到地址相关的异常,按照这个顺序排查,能省下大量时间:

排查项检查方法常见原因
广播里看不到设备抓包确认是否有广播报文协议栈未启动广播,或地址类型不支持当前广播类型
能搜到但连接不上抓包看连接请求是否被拒绝白名单未匹配、RPA解析失败、连接参数不兼容
连接后立刻断开查看控制器错误码和协议栈日志IRK缺失、地址解析失败、加密超时
地址在变化但不符合预期对照判断表确认地址类型配置了RPA/NRPA但业务只支持固定地址
手机端看到地址与抓包不同检查系统版本和权限iOS抽象、Android 12+地址限制

每次遇到“玄学”问题,先抓住包,再看日志,最后翻配置。这三步做完,90%的BLE地址相关问题都能定位到根因。

7.5 跨平台开发的注意事项

最后说说跨平台。很多人用Flutter、React Native开发BLE应用,地址处理的坑在不同平台上差异很大:

  • Flutter的flutter_blue_plus插件,在Android上能拿到device.id(即MAC地址字符串),但在iOS上拿到的是一个UUID。如果你用同一个代码逻辑判断“设备ID是否相同”,就会出现iOS重连逻辑失效的问题。
  • 方案:在应用层抽象一层“设备身份”模块。Android上优先用Identity Address(如果拿得到),iOS上用CBPeripheral.identifier结合广播数据里的自定义Identifier做双重校验。
  • 还要注意,部分Android机型在蓝牙关闭再开启后,扫描返回的MAC地址顺序可能变化,不要用“保存扫描顺序”来构建设备列表。

我把这些经验写出来,就是希望你能少走点弯路。BLE地址类型这个问题,说大不大,但一旦踩坑,往往要花几天才能爬出来。搞懂原理、掌握判断方法、配合抓包工具,你会发现,那些“会变的MAC地址”背后,其实都是清清楚楚的逻辑。

返回列表