ARTICLE DETAIL

资讯详情

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

Android蓝牙底层开发:从Framework到HAL的13节核心解析

Android蓝牙底层开发:从Framework到HAL的13节核心解析 1. 蓝牙更新13节——这套Framework到HAL的内容到底在讲什么先说个背景。我们这个系列一直盯着Android系统底层从Framework到HAL再到具体平台适配。标题里写蓝牙更新13节的时间是2019年6月5日那个时间点恰好是Android 9已经大面积铺开、Android 10还在预览的阶段车载、TWS耳机、蓝牙键盘手柄这类外设需求爆发式增长系统级蓝牙的问题比以往任何时候都多。做Framework开发的会发现应用层一个BluetoothAdapter能调起来的连接背后牵扯到权限校验、Binder通信、JNI、协议栈、HCI命令、HAL层最终落到一块蓝牙芯片的固件行为——任何一层出问题表现出来都只是连不上或声音断断续续。这13节的内容就是沿着这条链路往下打。从BR/EDR到BLE从A2DP音视频传输到HID人机接口设备从HCI日志分析到HAL接口适配。它不是给应用开发者教你怎么调BluetoothGattAPI而是面向需要在系统层面改蓝牙、排查疑难问题、做设备适配的人。我花了将近一个月把这13节全部跟完并且在自己手头的RK3568平台和高通平台上看代码验证下面把整个学习过程、重要知识点和踩过的坑一次性整理出来。如果你是下面这几类人这篇笔记应该对你有直接帮助Android系统工程师需要排查蓝牙配对、通话音频、BLE连接等底层问题BSP/HAL开发工程师需要对接或修改蓝牙HAL实现嵌入式玩家手里有HC05、ESP32这类蓝牙模块想搞清楚手机端到底是怎么跟它们交互的车载和盒子开发者被A2DP切SCO、HID键值这类问题反复折磨过2. 13节的全景拆解从蓝牙协议栈到HAL的完整贯穿先说一下这13节是怎么组织的因为这套编排顺序本身就很值得参考——它就是按照从用户可见行为向底层推进的思路设计的学完等于把Android蓝牙代码从外到内翻了一遍。2.1 前4节历史、架构和Framework入门前几节把背景铺得很扎实蓝牙从1.0到5.0每个版本的关键变化Android从早期用BlueZ到后来全面切换Bluedroid协议栈的来龙去脉经典蓝牙BR/EDR和低功耗蓝牙BLE两大类技术分支的区别。尤其建议认真看Android蓝牙整体架构这一节它把整个栈分层讲清楚了应用层BluetoothAdapter、BluetoothManager以及各个profile APIFramework Java层BluetoothManagerService、BluetoothAdapterService、BluetoothDevice、BluetoothA2dp等系统服务JNI层com_android_bluetooth_*Java和C相互调用的桥协议栈层Bluedroid对应btif/btc/bt_stack这一坨HCI层Host Controller Interface协议栈和蓝牙控制器之间的命令/事件通道HAL层libbt-vendor和系统调用控制芯片固件第一节就直说Android蓝牙是双栈架构——HCI上面是Bluedroid下面连接的是各家vendor的蓝牙控制器CSR、Realtek、AW、QCOM、Bestechnic等。也就是说你从Framework层改得再多底层芯片不支持也没用反过来芯片的HCI行为异常上层日志里只能看到超时和断链。这套双栈概念贯穿了整个13节后面的内容都是在这个分层里展开的。2.2 第5-7节经典蓝牙核心——SPP、L2CAP、A2DP、HFP这几节是BR/EDR的核心。SDP服务发现、L2CAP通道复用、RFCOMM串口仿真、A2DP音乐传输、AVRCP控制指令、HFP免提通话全部覆盖。重点留意A2DP和HFP这两节。音乐播放走的是A2DP它本质是双向的Source和Sink之间协商codecSBC、AAC、aptX之类然后通过L2CAP建立传输通道而打电话时用的是HFP协议音频从A2DP转到HFP建立的SCO/eSCO链路。这两条路的切换是整个蓝牙系统中特别容易翻车的一个环节13节里专门用了两节去讲它的路由逻辑后面我也会重点展开。2.3 第8-10节BLE协议栈——GAP、GATT、MTU、SMPBLE这几节含金量很足。GAP负责广播、扫描、连接建立GATT负责连接建立之后的服务/特征读写MTU协商决定了一次能传多少数据SMP则负责配对、密钥分发、安全加密。值得一提是MTU协商这一节有个细节很重要Android侧的默认MTU是23字节即20字节有效载荷很多开发者在BLE通信时发现一次读不到完整数据就是卡在MTU没变大。AndroidBluetoothGatt.requestMtu()在API 21之后就能用了但代码里必须等onMtuChanged回调成功后再发大包否则数据会被底层截断。这节里还对比了不同Android版本对MTU协商的差异Android 8以后协商成功率高很多旧版本有些设备上就只能靠L2CAP层固定缓冲。2.4 第11-12节HID设备和HAL接口HID人机接口设备这一节主要讲蓝牙键盘、鼠标、手柄、游戏手柄的接入流程。Android把蓝牙HID归到BluetoothHidDevice和BluetoothHidHost两类前者是让Android充当HID设备比如手机模拟鼠标控制电脑后者是让Android作为主机连接蓝牙键盘。很多电视盒子厂家头疼的遥控器适配问题就在这里。第12节开始进HAL。蓝牙的HAL接口在Android里经历了好几代早期是BluetoothHALlibbt-vendor后来演进到HIDL化的android.hardware.bluetooth1.1再下一篇是AIDL化的AGHAL。HAL层做的事情包括HCI命令下发、固件加载、地址管理、功耗控制以及一些芯片厂商自定义的私有命令透传。第12节用了一个真实的vendor实现当例子把hal_bluetooth.h里的接口一个个对过去看完对怎么从Framework调到底层芯片会有一个非常具体的认识。2.5 第13节实战日志分析与疑难问题排查最后一节是综合实战抓取一份完整蓝牙日志从logcat、dumpsys、btsnoop逐个看定位一个手机连车载蓝牙放音乐卡顿的真实问题。头一次把前面的理论全部串起来——最后发现卡顿的原因是A2DP的buffer大小和底层sco调度冲突要在HAL层调整interleaving参数。这13节的编排逻辑很像我之前带新人时给的路线先让人知道整个地图长什么样再从地图上的每一层深入最后带他走一遍完整的排查路线。对照这套目录去读AOSP源码你会发现自己能快速定位到具体文件和函数而不是一头扎进几百MB源码里瞎翻。3. 最容易绕晕的三个点A2DP切SCO、MTU协商、HID连接这套13节里头我印象最深、也是出现在很多工程现场的问题就是这三个。我分别展开尽量把本质讲透。3.1 A2DP切SCO为什么通话时声音突然退回去了先说现象手机连着蓝牙音箱放歌一切正常突然来了个电话音乐声停了通话声音从音箱里传出来但音质明显变差像对讲机。这是正常现象因为通话音频走的是HFP建立的SCO链路SCO链路是窄带或宽带语音CVSD或mSBC编码带宽固定跟A2DP那种高码率、可变码率的媒体传输不是一回事。Android侧的架构里A2DP和HFP分别是两个独立的profile服务但它们在音频HAL层共用同一个音频通路。Framework里负责决策的是AudioPolicyManager当电话状态变化时它会通知BluetoothManagerService进而触发A2DP暂停、HFP创建SCO。从logcat看你会看到这类日志audio_policy: getOutputForAttr: device BT_A2DP_OUT - BT_SCO bluetooth_audio: session switched from A2DP to SCO btif_hf: profile state changed: CONNECTED, state7在实际排查中最头疼的是切换不回来。常见原因有这些通话结束后AudioPolicy没有恢复A2DP输出表现为音乐一直在暂停状态HFP AT指令协商失败SCO迟迟无法建立导致手机显示通话音频已连接但没有声音某些国产平台对SCO的采样率支持不全mSBC 16kHz没走通只能回落到8kHz CVSD音质明显发闷我之前遇到一个RK3568平台的通话蓝牙噪声问题就是典型的本能在HAL层。平台接的是AP6275S蓝牙芯片打电话时SCO链路通了但声音里夹杂着周期性的爆音。从btsnoop看HCI事件没有任何异常HFP指令也正常最后查到是音频HAL里SCO的I2S引脚和WiFi共用了一条MCLK线蓝牙和WiFi并发工作时的时钟抖动直接灌到SCO音频上。这个问题你只盯Bluedroid永远找不到答案必须把视野放到HAL和平台音频路由层。3.2 MTU协商一次最多传20字节是谁定的MTU在BLE里有明确的机制链路上默认MTU是23字节20字节有效载荷3字节L2CAP头Android的requestMtu()本质上是通过L2CAP的Connection Parameter Update协商把MTU变成更大的值最大值受控制器能力限制通常BLE芯片能支持247。实际开发中经常踩的坑是这些在onConnectionStateChange回调里马上requestMtu()但连接刚建立时底层通道还没就绪很多设备上这个请求会失败。部分外设不支持MTU协商onMtuChanged永远不来你需要给个超时兜底。就算MTU协商到了247也不代表你可以在一个write请求里塞247字节——Android的BluetoothGatt.writeCharacteristic在协商完成后可以传最多mtu-3字节但如果特征本身设置了Write Without Response某些外设会限制单包大小没响应的写法容易丢包充分沟通后还是建议做分包和应答确认。我用过一个很直观的类比讲给团队新人听MTU就像水管粗细23字节是默认细细的水管想传输大文件就要换粗水管但换粗水管之前你不能先猛灌水否则水会倒灌回来数据被截断。3.3 HID键盘手柄连接不上多数是配置问题蓝牙键盘、手柄这类HID设备Android的设备端日志里常见问题Framework扫描到设备但配对弹窗不出现配对成功但键值映射不对某些按键没反应连接一会儿就断开报BtGatt.GattService: connectionTimeout第一类问题通常是PROFILE_CONNECTOR优先级没设置。代码里要确保HID的profile优先级高于默认否则手柄被连接后会被判定为低优先级设备系统在资源紧张时直接把它踢掉。第二类问题的根源在HCI层键值映射和led事件调试时要打开hci snoop log看HID Report描述符是否正确下发。有一定概率是外设的Report Descriptor本身不规范Android解析到一半就放弃了。第三类问题我遇到过最典型的场景是某些游戏手柄协议栈在idle时会休眠但Android的BluetoothHidHost不会自动重连表现为一放下十分钟再拿起来就没反应了。排查时先去Settings里看蓝牙设备详情有没有重新连接开关再不行就要在应用层额外做连接监控。4. HAL层接口与Vendor适配看懂蓝牙芯片的控制边界HAL层是Framework和芯片固件之间的分界线也是整套代码里最玄的地方。很多人问为什么改Framework有时不解决问题最后还得让芯片厂商出patch答案就在HAL这层。4.1 什么是蓝牙HAL它和嵌入式HAL库不是一回事先说一个常见误区。很多人搜HAL的时候会看到STM32的HAL库比如hal库驱动dht11、hal库驱动oled这类以为跟Android的HAL是一回事。英文缩写确实一样但语境完全不同STM32的HAL库是芯片厂商提供的硬件外设驱动函数库Android的HAL则是系统框架与硬件驱动的中间接口层。理解了这一点你去看hardware/libhardware/include/hardware/bluetooth.h时就不会被绕晕。蓝牙HAL在Android代码里主要负责这些事初始化加载固件、设置蓝牙地址、打开UART/USB/SDIO/PCIe与蓝牙控制器的物理通道指令下发把HCI命令包从Bluedroid下发到物理传输层事件上报从物理层读取Event和ACL数据上报给协议栈私有命令芯片厂商扩展的HCI vendor command用来做校准、功耗管理、产测等4.2 HIDL到AIDL的演进在Android 8-10时代蓝牙HAL走的是HIDL接口包名是android.hardware.bluetooth1.0和android.hardware.bluetooth1.1核心方法就是initialize、start、stop这几个真正的数据通路在BluetoothHci接口里。到了Android 11以后谷歌开始推行AIDL化的HAL即AGHALAIDL Generic HAL把传统的hal_bluetooth.h那套C接口全面改成AIDL接口。这个改动对做BSP的人影响很大之前直接改libbt-vendor就能实现的功能现在要同时适配HIDL到AIDL两层。很多平台厂商的升级patch里一半的工作量都花在这套接口迁移上。学习这一节建议直接打开AOSP对应目录对照看hardware/interfaces/bluetooth/ hardware/interfaces/bluetooth/1.0/default/ hardware/interfaces/bluetooth/1.1/default/看它的默认实现怎么把HCI包从socket搬到物理传输层就理解了HAL是一层壳这句话的真义——它本身不实现蓝牙协议只负责搬运HCI数据和加载固件。4.3 Vendor层芯片私有HCI和产测指令HAL层再往下就不是AOSP源码管得到的了是各家芯片厂商的闭源库或者平台代码。高通那边对应bt-hci和wcnss瑞芯微平台对应AP6275S等模组的libbt-vendorRealtek对应rtkbt的HCI扩展博通/Cypress对应btlpm——这些模块都藏在HAL的Vendor实现里。实际上当你遇到下面的问题基本就要进入Vendor层排查蓝牙开关打不开可能是固件加载失败功耗异常蓝牙一直在扫描或未进入sniff模式兼容性问题某朵耳机连不上但其它耳机正常产测指令未实现工厂测试的HCI vendor command返回失败这块没有标准文档只能判读Vendor的日志。好在HCI层有统一的日志格式通过hci snoop抓到的log在Wireshark里可以直接看指令和事件的对应关系。建议每个做平台移植的团队都建立一套自己的HCI vendor命令速查表不然每次都要翻厂商的协议文档。5. 蓝牙问题的实战排查链路日志、btsnoop和代码定位排查蓝牙问题最忌讳的是一上来就翻源码。正确的顺序应该是先确定故障现象对应的协议/功能然后通过日志把链路逐层“点亮”最后才去改代码。13节里最后一节讲的就是这套方法论我把实际操作步骤拆开来说。5.1 抓日志四件套logcat、dumpsys、btsnoop、HCI trace第一件是logcat。重点过滤Bluetooth*、bt_btif、btif_*、btif_hf、BTA_*、A2dp*、audio_hw_primary这些tag。adb logcat -v threadtime | grep -E Bluetooth|btif|BTA_|A2dp|HFP|GattService第二件是dumpsys bluetooth_manager。这个命令能输出当前蓝牙状态、绑定设备列表、profile连接状态、以及各profile的优先级。排查为什么自动重连不生效的时候这个命令很有用。第三件是btsnoop。这是蓝牙层的“网络抓包”最关键。Android开发者选项里默认关闭需要手动打开adb shell settings put global bluetooth_btsnoop_log_quality_mode 1 adb shell settings put global bluetooth_hci_log 1打开后日志会生成在/data/misc/bluetooth/logs/btsnoop_hci.log导出后直接用Wireshark打开能看完整的HCI Command/Event/ACL数据。很多“连不上”“断链”“丢包”的硬件问题在这里一眼就能看出来。第四件是bugreport。它会把系统所有服务的状态快照打包包含蓝牙相关的各profile状态、audio_policy的状态、射频相关状态适合排查跨模块的诡异问题。实际排查时我的固定套路是先看dumpsys bluetooth_manager确认Profile连接状态和优先级再用logcat看Framework层的报错和流程如果涉及音频抓一份btsnoop看A2DP/HFP/HCI的事件是否正常定位到HAL层再抓vendor log通常是开UART或者htcisnoop的厂商接口5.2 结合真实案例通话蓝牙噪声的完整定位顺序之前遇到过一个RK3568 AP6275S 通话蓝牙噪声的问题当时的定位过程完美体现了这套链路的方法。现象是连蓝牙耳机打电话对方听到每秒钟一次轻微爆破声。排查顺序第一步确认不是耳机的锅换三副耳机都一样。第二步抓btsnoop看SCO链路有没有重传、丢包、乱序——结果HCI层干干净净没有retransmit。第三步去dumpsys audio和logcat看蓝牙音频HAL的路由发现蓝牙SCO走的是平台I2S0口而WiFi芯片也挂在I2S0附近。第四步做交叉验证关闭WiFi后再打电话噪声立刻消失锁定是I2S时钟串扰。最后在HAL配置里调整了SCO使用的时钟源和驱动强度问题解决。这个过程如果用一句话概括永远不要在HCI层没问题的时候就急着改Framework源码先在日志里分清是协议层、传输层还是物理层的问题。一个可靠的排查者最擅长的是判断这个问题该在哪一层问。5.3 常见的日志误判和陷阱有几种日志现象容易被误判btif_hf: state changed: CONNECTED出现不代表SCO音频已经建立成功这只是HFP服务层的连接后面器材需要继续看SCO/ESCO connected事件。GattService: onConnectionStateChange回调了STATE_CONNECTED不代表MTU协商完成要等onMtuChanged回调用完才能确定链路完全可用。A2dpSinkStreaming状态正常但音频卡顿——不一定是蓝牙链路的问题可能是音频HAL的resampler在作怪。logcat里看到大量的Unexpected event比如avdtp收到不期望的包不要急着判协议栈bug很多国产芯片的HCI实现就是会在特定状态下发额外事件看厂商的已知问题列表往往更快。这些判断都是在一次一次踩坑中积累的。13节里最宝贵的并不是知识本身而是它把什么现象对应什么层级这件事讲透了。6. 学习这套内容时的亲测经验和避坑建议最后写几个我自己的实操体会都是代码之外的东西。第一跟着目录去敲代码之前先建立一份自己的蓝牙接口速查表。把BluetoothAdapter、BluetoothManagerService、btif接口、hal接口的关键函数名和文件路径列在一张表里。学习过程中每遇到一个接口就往表里填一行。到后来排查问题时这张表比任何源码文档都管用。第二反复练习从现象倒推代码路径的思维。比如看到Google搜索词里的蓝牙A2DP切SCO模式、蓝牙模块MTU、蓝牙HID人机接口设备这些都是别人真实遇到的坑。你完全可以当面试题来训练如果用户告诉我手机连蓝牙音箱打电话声音断断续续我应该先看哪份日志先查哪个类我习惯的做法是每人给一个场景然后限时10分钟要求写出从logcat到btsnoop再到HAL层的排查计划。这个方法用在带新人身上效果明显。第三不要忽视硬件和嵌入式玩家发来的问题。我看到热搜词里大量出现HC05蓝牙模块连接不上、hal库驱动dht11、hal库驱动oled这类内容表面上和Android Framework无关但实际上很多做Android系统的工程师是从嵌入式那一边转过来的反过来玩STM32的人对HCI、GATT、SPP的理解往往很扎实。理解模块端的行为帮助你反过来理解Android主机端的处理方式。比如HC05模块连不上的问题常见原因是掩码回复的模块固件默认波特率不对——这跟Android侧有什么关系有很多蓝牙故障的根源恰恰是物理串口/UART的波特率激励参数在HAL层初始化时没有和模块端匹配上。第四掌握几个直接可复用的快速检查命令。这个值得存在笔记里# 查看蓝牙开关状态和profile连接 adb shell dumpsys bluetooth_manager # 重置蓝牙状态很多时候比开关一次飞机模式更好用 adb shell svc bluetooth disable adb shell svc bluetooth enable # 抓取hci日志开关 adb shell settings put global bluetooth_btsnoop_log_quality_mode 1 # 查看当前蓝牙连接设备的详细信息 adb shell dumpsys bluetooth_manager | grep -A 30 mConnectedDevices第五学习过程中一定会遇到一个微妙但致命的问题Android版本差异。2019年那会儿的主流是Android 9/10HIDL还是主流但转到Android 12以后AGHAL的AIDL接口已经全面铺开了。我现在看代码的习惯是同时打开两个分支的源码对比着看一个选项目实际平台版本一个选最新的AOSP master这样既能解决眼前项目的问题也能提前知道下一代接口要怎么迁移。第六这一行的核心能力其实是建立链路感。Framework层改一个权限、HAL层改一个参数、物理层换一根天线最终体现在用户体验上的结果可能是同一个现象。没有链路感的人遇到问题只能猜有链路感的人看一眼现象就知道该去哪个目录找代码。这13节本质上就是在帮你把这条链路焊扎实。如果你的项目正好在做车载、盒子、TWS耳机、蓝牙键盘手柄的适配我建议按这个目录在自己的设备上逐节去验证。源码在AOSP里都是开放的日志工具也都在开发者选项里剩下的就是把每一个知识点真实地动手过一遍。
返回列表