别的不说,做蓝牙开发或者智能硬件的朋友,只要是和手机端联调,就一定绕不开一个问题:两端明明配上了,但数据就是传不过去,或者连接时好时坏。你问对方用的什么协议、怎么做重传的,大概率得到的答案是“我也不清楚,就这样写的”。这种时候,光靠肉眼盯日志和反复猜,效率太低,最直接的办法就是把蓝牙底层的 HCI 数据包抓出来看。
这篇文章就来说说在小米手机上怎么抓btsnoop_hci.log,以及从开启开发者选项开始的一整套蓝牙调试流程。我会把每一步的操作意图和背后的原理拆开讲,不是单纯甩给你一串步骤,而是让你知道为什么要这么做,做完之后怎么用抓到的日志去定位问题。前前后后我自己是踩了不少坑,今天一并整理出来,给自己做个备忘,也给有同样需求的朋友一个可直接照抄的完整方案。
1. 动手之前先把概念理清:btsnoop_hci.log 到底是什么
很多人在网上搜“蓝牙抓包”会看到一堆工具名,比如 Ellisys、Frontline,这类专业的蓝牙协议分析仪动辄几万块,不是所有人都能接触到。实际上,手机系统内部早就内置了一个抓包能力,只要打开开关,它就会把蓝牙控制器和主机之间交互的 HCI(Host Controller Interface)数据包全部记录到一个文件里,这个文件就是btsnoop_hci.log。
1.1 它和系统蓝牙日志有什么区别,别混淆
小米手机的系统日志里其实有两类跟蓝牙相关的记录。一类是logcat里的蓝牙日志,主要输出的是蓝牙协议栈上层的信息,比如连接状态变化、GATT 服务和特征值的读写请求,这些是 Android 系统自己打印的调试信息。另一类就是btsnoop_hci.log,它记录的是蓝牙芯片和主机之间真实的交互数据包,属于链路层和主机控制层的原始记录。
打个比方,前者像是快递公司的客服通话录音,能告诉你“快递到了”“客户拒收了”“改地址了”这些业务节点;后者则是运输车辆的行车记录仪,能看到刹车、转向、车速这些硬件层的真实动作。当你遇到蓝牙连不上、配对成功后立刻断开、数据吞吐量异常这类问题,只看 logcat 往往只能看到“连接断开”这个结果,看不到断开的根因,而 HCI 日志里会把是你主动断的、还是对端断的、断开的原因码是什么全部记录得一清二楚。
1.2 拿到它能干什么,哪些场景最需要
我平时用btsnoop_hci.log最多的场景有三个。
第一个是调试自研的 BLE 设备。设备端是自己写的固件,手机端是调试工具 App,两边对接的时候经常出现“手机显示已连接,但设备数据死活不上报”。这时候抓一份 HCI 日志,就能看到 ATT 层的请求和响应有没有正常往来。如果设备收到了请求但没回,那就是设备固件的问题;如果手机压根没发出请求,那就是 App 的调用姿势不对。
第二个是排查连接不稳定的问题。比如蓝牙耳机用了十几分钟就开始断断续续,或者智能门锁偶尔打不开。HCI 日志里会有断开的 reason code,比如 0x08 表示 Connection Timeout,0x13 表示 Remote User Terminated Connection,根据这个就能初步判断是信号问题还是对端主动断开的问题。
第三个是做性能调优。BLE 的实际吞吐量受 MTU 大小、连接间隔、从机延迟这些参数影响,这些参数协商过程的实际值,不会直接暴露给应用层,但全部记录在 HCI 日志里。通过抓包,你可以确认自己的设备到底协商到了什么参数,而不是靠猜。
2. 小米手机调试前的环境准备
这一节做的事情比较基础,但基础不牢会给你后面添不少麻烦。开发者选项没开,后面一切免谈;USB 调试模式没设置好,日志导出的时候够你折腾半天。
2.1 开发者选项怎么开启,各版本差异注意一下
小米手机开启开发者选项的路径迭代过几次。MIUI 12 及更早的版本,在“设置—我的设备—全部参数”里连点“MIUI 版本”7 次就能开启。到了 MIUI 13 之后,包括现在的澎湃 OS(HyperOS),“全部参数”页面里显示的可能是“OS 版本”,你连续点击它就行。要注意的是,有的机型更新系统之后开发者选项会被自动隐藏,需要重新点一遍,这个不是 bug,是系统为了减少误操作做的保护措施。
开启之后,“设置—更多设置”最下面会多出“开发者选项”入口。进去之后,我习惯先把“自动系统更新”关掉,防止半夜系统自动升级把 ROM 换掉导致环境不一致,做调试最怕的就是环境漂移。
2.2 让蓝牙调试更顺手的几个系统设置
在开发者选项页面里,跟蓝牙调试直接相关的开关有这么几个,建议按下面的方式设置:
- “蓝牙数据包日志”开关,这个是最核心的,打开之后系统才会开始记录 HCI 数据。不同机型的翻译可能略有差异,有的叫“蓝牙 HCI 日志”,有的叫“蓝牙信息日志”,认准带 HCI 或者 btsnoop 字样的就行。
- “USB 调试”开关,保持开启,后面导出日志文件需要用到。
- “USB 配置”选项,选择“USB 以太网”或者“MTP”,这个根据你导文件的方式灵活选。如果要用 adb 命令导出,通常不需要特意切,但如果在文件管理器里看不到内部存储,就切成 MTP。
- “不锁定屏幕”开关,建议顺手打开,防止抓 log 抓一半手机锁屏,某些情况下锁屏会导致蓝牙进入低功耗模式,影响问题复现。
这几个设置花不了两分钟,但对后面的操作影响很大。拿“USB 配置”来说,我看到不少新手把手机连上电脑后,发现电脑里看不到内部存储的文件夹,急得不行,其实只要在开发者选项里把 USB 配置改成“MTP(多媒体传输)”就能解决。
2.3 USB 调试与网络调试的选择,我推荐双保险
在电脑上执行 adb 操作,有 USB 连接和无线网络连接两种方式。USB 连接稳定、速度快,但缺点是手机得一直插着线;网络调试会方便很多,但第一次连接还是需要 USB 线来授权。
我的习惯是:先在 USB 模式下完成所有初始化工作,包括安装必要的调试工具、确认设备能被识别,然后通过 adb 命令切换到 TCP/IP 模式,把线拔了用无线网络调试。这样做的好处是,当你需要在真实环境中跑测试、手机要被拿着走来走去的时候,日志文件会持续记录到手机里,最后你再插线导出就行。
注意:无线 adb 的端口每次重启设备都会变,而且有些小米机型在开发者选项里关闭“USB 调试”再重开,无线调试的授权也会失效。如果发现
adb devices列表是空的,别慌,重新插线授权一次就好。
3. btsnoop_hci.log 的完整获取流程
前面的准备工作都是为了这个核心环节。整个获取流程其实只有三步:打开蓝牙 HCI 日志开关、复现问题、导出日志文件。但每一步都有细节在里面,走错一步得到的就是一份没用的数据包。
3.1 开启蓝牙 HCI 日志抓取,这一步决定日志有没有
在开发者选项页面,拉到底部找到“蓝牙数据包日志”这个开关,打开它。系统会弹一个提示,告诉你打开后会影响蓝牙体验并增加电量消耗,直接确认就行。这里有个特别重要的点:这个开关必须在问题复现之前打开,而且打开之后最好重启一下蓝牙,让系统重新初始化蓝牙协议栈,确保 HCI 日志的抓取进程在这个新会话里开始工作。
如果你是在问题已经发生之后才想起开这个开关,那基本这一次是白抓了。HCI 日志只记录开关打开之后产生的数据包,之前的交互过程一个字节都不会在里面。
还有一点容易被忽略:有的机型在开启了“蓝牙数据包日志”之后,你连接蓝牙设备时会感觉到反应变慢,这是正常的。因为系统要把蓝牙交互过程中的 HCI 事件全部经过日志模块记录下来,会带来微小的额外开销。调试完记得把开关关掉,免得一直耗电。
3.2 复现问题与操作注意事项,让问题“可重复”
日志开关打开之后,接下来就要开始复现问题了。这里有几条经验我觉得值得说一下。
第一,尽量把问题复现的步骤收敛到一个可重复的路径。比如你的问题是“连接后 30 秒自动断开”,那就把手机和设备放在固定距离,从连接开始计时,观察断开的时刻。不要边走路边测,信号强度变化会导致问题发生的时间点飘忽,后面看日志会看得一头雾水。
第二,在复现过程中减少无关的蓝牙操作。不要一边测 A 设备,一边让另一个蓝牙耳机在旁边反复重连。HCI 日志记录的是所有蓝牙活动,无关的交互会把关键信息淹没,增加分析难度。
第三,如果条件允许,在复现问题的时候同步用另一个设备记录时间。比如用电脑开个计时器,在手机 App 里观察到的异常现象,尽量记下大概的时间点。HCI 日志里所有数据包都有时间戳,这个时间点对应过去,能帮你精准定位到问题发生前后的那几十个数据包。
复现问题之后,等个几秒钟再结束操作,确保日志文件里包含完整的断开过程。很多时候断开事件不是立刻发生的,多等一会儿能记录到更多的协议栈处理细节。
3.3 日志文件的导出方法,两种方式各有优劣
日志文件在手机内部存储的位置通常是内部存储/Android/data/com.android.bluetooth/files/btsnoop_hci.log,不过用文件管理器直接翻到这个路径,在很多小米机型上会遇到权限限制,因为 Android 11 之后对Android/data目录的访问限制越来越严格。
我推荐用 adb 命令来导出,方便快捷,不受权限限制。手机连上电脑,在终端执行:
adb pull /sdcard/Android/data/com.android.bluetooth/files/btsnoop_hci.log ./把文件拉到你当前目录。如果显示权限不足,可以先执行adb root,再用上面的 pull 命令。需要注意,adb root只对部分开发版系统有效,稳定版的 MIUI 和 HyperOS 一般不开放这个命令。
如果你的电脑上没装 adb,也可以试试用系统自带的文件管理,在“下载”或者“根目录”里搜索btsnoop_hci.log这个文件名,有的机型在“蓝牙”设置页面里也有“分享蓝牙日志”的入口。不过这个方法在部分新机型上不稳定,保底方案还是装一个 adb。
提示:如果你抓完日志之后又做了很多其他蓝牙操作,建议在导出的同时重新开一份日志。一份大小适中的 btsnoop 文件通常也就几十到几百 KB,如果文件特别庞大,说明日志里包含的无关内容太多,分析的时候会很费劲。
4. 用蓝牙调试助手做二次验证,让日志会说话
抓到了btsnoop_hci.log,不等于问题已经定位到了。日志是给行家看的,如果你对蓝牙协议不熟悉,一行一行去读 HCI 数据包会很痛苦。这时候,一个趁手的调试助手 App 能帮你把问题快速复现,顺便把日志和上层行为对应起来,这也是我在文章标题里提到的“小牛蓝牙调试助手”这类工具的用武之地。
4.1 蓝牙调试助手能干什么,补足日志抓取的盲区
小牛蓝牙调试助手这类工具,本质上是一个 BLE(低功耗蓝牙)客户端,可以扫描周围的蓝牙设备、发起连接、遍历服务和特征值、直接读写数据。它和btsnoop_hci.log是互补的关系:日志文件告诉你底层发生了什么,调试助手帮你主动构造数据包、验证你的假设。
举个例子,你怀疑设备端的某个特征值没有正确响应写操作。你可以用调试助手手动往这个特征值写一段数据,看设备有没有回包。如果助手这边能正常收发,说明设备端没问题,问题出在你自己 App 的代码;如果助手这边也收不到回包,那问题基本就在设备端。这样一测,问题边界立刻清晰了。
不只是验证问题,调试助手还可以用来获取设备的完整特征值列表,这在你没有设备端源码、只能通过逆向协议做对接时格外有用。你把设备连接上,挨个特征值试,根据设备的表现反推每个特征值的作用。
4.2 辅助日志分析的配合使用流程
我的常规操作流程是先用调试助手把问题复现一遍,确认“不是我的 App 独有的问题”,然后再打开手机的原生录音记录 HCI 日志,再做一次同样的操作,最后把日志导出。
这样做有一个额外的好处:调试助手通常会显示当前连接的信号强度(RSSI)、MTU 大小、连接的间隔等参数,这些信息在 HCI 日志里也能看到,但助手把它们以易读的方式展示出来了。你可以在助手里记下这些数值,再对照日志里的数据包确认,两头看互相印证。
如果用的不是现成的调试助手,而是自己在开发的调试工具,也可以在工具里加一个“导出 HCI 日志后自动解析”的接口。我见过有团队把 btsnoop 文件的解析集成到自己的工具链里,把关键事件自动提取出来报表格式展示,这样哪怕是测试人员,不需要深入理解蓝牙协议也能一眼看出连接断在了哪个环节。
5. 日志分析的基本方法与常见问题排查
日志拿到手了,接下来就是最关键的部分:怎么从一堆十六进制数据里找到问题。如果你没有现成的解析工具,我建议先用 Wireshark 打开btsnoop_hci.log,它能自动识别格式并解析出 HCI 事件、ACL 数据、L2CAP 层、ATT/GATT 层的信息,把层级结构清晰地展示出来。
5.1 用 Wireshark 打开 btsnoop_hci.log 看什么
打开文件之后,Wireshark 会按照数据链路类型识别文件。你不需要理解每一行,关键是先建立过滤条件。我一般先看三块内容。
第一块是连接事件相关的内容,过滤btrfcomm或者直接看HCI_EV类型的数据包。这类数据包里包含了连接建立、断开、参数协商和各类状态变更事件,断开原因码也是在这里面找。
第二块是 GATT 层的数据交互,过滤条件用btatt。这里能看到所有读写特征值的请求和响应。如果你的设备有 10 个特征值,但 App 只尝试读其中 3 个,说明你的 Service Discovery 可能没做完整,很多 BLE 连接“找不到服务”的问题都是这一层暴露出来的。
第三块是 L2CAP 层,过滤btl2cap。这里能看到逻辑信道和连接参数更新的请求。有些设备会因为连接参数不合理被手机拒绝更新,导致高数据量传输时疯狂丢包,这类问题在 L2CAP 层表现得很明显。
提示:Wireshark 对流量过滤的语法很有用,比如
btatt.opcode == 0x12可以筛选出所有写请求。这对定位“为什么数据写不进去”能省很多时间。
5.2 几个高频异常特征与排查方向
下面列几个我在实际抓包中经常遇到的异常特征,以及对应的排查方向。
第一,连接建立成功之后马上一堆Disconnect事件,原因码是0x3E或者0x08。0x3E 表示对端设备不支持请求的特性,多数是你请求了一个设备端没实现的服务或者特征值。0x08 表示连接超时,大概率是设备实际距离太远或者天线功率太低,也有可能是设备在广播之后没有真正在监听连接请求。
第二,Read By Type Request发出去之后,一直没有对应的Read By Type Response,然后过一会儿连接超时了。这种情况多半是设备端的 GATT 服务没有正确初始化,或者设备的协议栈在处理长特征值时出了死循环。我之前调试过一个血压计,就是这个特征,后面发现是固件里的 GATT 服务注册顺序有问题。
第三,MTU 协商的结果不符合预期。发送Exchange MTU Request请求 247,响应的值却是默认的 23。如果你的数据传输非常慢,先确认一下是不是这个问题。有些设备虽然硬件上支持更大的 MTU,但固件里写死了不允许更新。这不算 bug,但你需要知道实际值,否则按 247 做分包逻辑会出大问题。
第四,连接参数更新请求被拒绝。设备端主动发出Connection Parameter Update Request,但主机侧回了个L2CAP Command Reject。很多 BLE 外设为了省电会把连接间隔调大,但如果当前连接正处于数据传输繁忙状态,主机会拒绝参数更新,这是协议栈的正常行为。你可以根据这个表现去调整 App 的定时器策略。
5.3 常见问题速查表
| 现象 | 日志特征 | 可能原因 | 排查方向 |
|---|---|---|---|
| 搜索不到设备 | 无广播包或广播包不符合过滤 | 广播参数错误/设备未开启广播 | 检查广播类型、广播间隔、MAC 过滤 |
| 配对失败 | Authentication Failure 原因码 | PIN 码不匹配/配对方式不支持 | 核对配对方式、确认设备端 IO 能力 |
| 连接后秒断 | 原因码 0x13 | 对端主动断开 | 检查设备端连接回调处理逻辑 |
| 数据写不进去 | 特征值不支持写属性 | 特征值权限不对 | 查看设备端特征值属性和权限位 |
| 数据收发很慢 | MTU 值 23、连接间隔较大 | 未协商 MTU / 连接参数未优化 | 主动发起 MTU Exchange、调整参数 |
| 周期性断开 | 原因码 0x08 | 超时无响应 | 检查设备端是否周期性休眠 |
| GATT 服务为空 | 没有 Services Discovered | 服务表未正确注册 | 检查设备端 GATT 服务初始化流程 |
这个表基本覆盖了我日常比较常见的问题。要注意的是,表里的“可能原因”只是方向性提示,具体问题还是要结合你的业务场景和设备端实现来确认。HCI 日志能帮你缩小范围,但不能直接告诉你代码该怎么改。
5.4 一个完整的问题定位案例
拿我最近调的一个智能门锁来说,用户反馈偶尔关不上门,但 App 显示连接正常,也没有提示错误。
抓了一份 HCI 日志,发现流程里有大量Connection Parameter Update Request,设备端把连接间隔从 30ms 调到 500ms,然后又调回 30ms,反复多次。在最后一次调整之后,出现了Connection Timeout,说明手机没能在超时时间内收到设备端的任何数据包,这才触发了断开。
进一步看,设备端是在用户按“关门”按钮之后才开始高频请求调整连接参数。调整过程中如果收到门锁的 IO 中断,固件里的 BLE 栈可能被占用了,导致链路层没能及时回复手机的包。后面找设备端的人确认,确实是固件在关门动作里加了太多阻塞操作,把蓝牙协议栈的任务挤掉了。
这个案例想说明的是,btsnoop_hci.log不只是给网络工程师用的,做上层应用开发的人也能通过它快速确认问题归属。你不用懂每一个数据包的含义,只要能把关键事件串起来,就能少踩很多“我这边没问题”“你那边没问题”的沟通黑洞。
6. 一些属于经验范围的内容
调试蓝牙问题,工具链的熟练程度很重要,但更重要的是排查问题的思路和方法。
我的建议是,先确认问题的可复现性,再确定排查方向。可复现的问题用 HCI 日志定位最有效,因为你有足够的时间去“案发现场”记录完整的数据交互。偶尔出现一次的问题,纯靠抓包碰运气,效率很低,这类问题我一般倾向于在设备端加更细的日志,把异常状态打印出来,再从上层往下层查。
拿到日志之后,先梳理大节奏,再抠细节。大节奏是连接流程的关键节点,广播、发现、配对、连接、服务发现、数据交互、断开,这些节点像路标一样把流程切成几段,先确定异常发生在哪一段,再针对这一段去翻具体的数据包。直接一头扎进十六进制数据里,很容易盯到眼睛疼还找不到问题。
日志分析这个技能,平时不用会生疏,但需要用时最好能一次到位。我建议你把每台设备的问题处理过程和关键日志都留个档,时间久了就是自己的问题特征库。有些问题看起来毫无关联,其实设过一段时间,就会发现它们之间的规律。
最后分享一个小技巧:小米手机在开启“蓝牙数据包日志”后,如果你不想每次导日志都插线,可以配合无线 adb 在手机上直接执行抓取。把日志文件直接
adb pull到电脑上,比在文件系统里反反复复找“内部存储”路径要快得多。这个习惯养成之后,日常调试流畅度会提升一大截。