做高通耳机 App,如果只面对单一设备、单一协议版本,事情通常还没那么复杂。你知道设备支持什么命令,知道 characteristic 怎么收发,知道回包长什么样,按这套协议一路写下去,很多功能都能跑起来。
真正让项目开始变重的,往往不是 BLE 本身,而是协议版本开始共存。
一旦项目里同时要考虑 GAIA v1/v2/v3,Android 端就不能再把协议当成一份固定规则来写了。这个时候最怕的不是“多几个 if”,而是你没有意识到版本共存本质上是在逼 App 做协议分层。
如果这一步没做好,前期也许还能勉强跑,后面设备型号一多、功能一多、OTA 一接,整个代码就会迅速变成一团:命令发错版本、回包走错处理器、初始化顺序互相干扰,最后你看到的就不只是业务 bug,而是整套协议系统开始失控。
高通这类耳机项目最容易踩的坑,就是一开始把协议理解成“BLE 上面的一组命令码”。这种理解不算完全错,但只够支撑早期 demo。真到了多版本共存阶段,GAIA 更像一套完整的通信框架,而不是几条命令。
从你现在这个项目里,其实已经能很明显看出这种分层思路。
最底层是传输层。代码里有 LeGattClient 和 RfcommClient,也就是 BLE 和 RFCOMM 两种不同链路。这个设计本身就说明,协议层不应该直接绑死在某一种蓝牙传输上。因为对于上层来说,重点不是“底下是 BLE 还是经典蓝牙”,而是“有没有一条稳定的数据通道能把 GAIA 包送上来、送下去”。
这一步如果不拆,后面协议逻辑就会和 BLE characteristic、RFCOMM socket 这些细节硬耦合在一起。短期看省事,长期看几乎必炸。
再往上一层是收发层。你项目里有 GaiaReader 和 GaiaSender,这层很关键。因为它把“底层收发字节流”变成了“上层读写 GAIA 包”。
这个抽象听起来普通,但实际上非常重要。协议系统如果没有独立的 reader / sender,后面所有版本逻辑、vendor 逻辑、plugin 逻辑都会被迫围着原始字节写。只要某一层直接碰底层字节,系统边界很快就会开始模糊。
协议分层真正开始体现价值的,是版本层。
你项目里同时有:
-
V1V2Vendor -
V3Vendor
这个结构其实就已经给了答案。
当 GAIA v1/v2/v3 共存时,Android 端最重要的一件事,不是“兼容所有命令”,而是先把不同版本的协议处理入口拆开。
因为版本共存最怕的事情就是混用。
比如某个设备上报的是 GAIA_VERSION = 3,但你的某个初始化流程还在按 v1/v2 的处理逻辑走;或者某段业务代码根本不管当前版本,直接按老的 packet 格式去拼命令;再或者某些插件初始化时默认自己一定跑在某个版本之上,结果遇到另一种设备就开始状态错乱。
这种问题如果只是偶尔出一个,很难定位。因为表面现象可能只是:
-
某个命令没响应
-
某个设备状态不更新
-
某个插件一直 ready 不起来
-
OTA 在某一步失败
但根因其实是版本路由错了。
所以协议分层的第一原则其实很简单:
先判断版本,再分派能力,不要让业务逻辑自己猜版本。
这一点你项目日志里已经有很直观的证据:
GT BLE device info: info=GAIA_VERSION, update=3
[onStarted] stopping V1V2Vendor, gaiaVersion=3
这两句说明当前实现做对了一件很重要的事:连接建立以后,不是让所有 vendor 都同时尝试接管协议,而是先识别版本,再决定谁该退出、谁该继续工作。
这个模式其实就是版本分层最核心的思想。
不是所有逻辑都支持所有版本,而是每个版本应该有自己的边界。
从架构上看,我觉得 v1/v2/v3 共存时,Android 端至少要拆成这几层。
第一层是 Transport Layer。
这一层只负责链路本身,比如 BLE GATT 还是 RFCOMM,连接状态怎么回调,断开怎么恢复,底层字节怎么发出去。它不该知道什么是 v1/v2/v3,也不该关心某个命令是不是升级命令。
第二层是 Protocol IO Layer。
这一层负责包的读取和发送,也就是类似 GaiaReader、GaiaSender 这种角色。它的职责是把 transport 提供的字节流变成可处理的协议输入,把上层生成的协议输出重新送回 transport。
第三层是 Version Routing Layer。
这一层非常关键。它应该负责:
-
当前设备 GAIA 版本识别
-
不同版本实现的启停
-
当前协议上下文归属
如果没有这层,版本判断很容易散落到各个业务类里,最后变成 everywhere 的 if (version == 3) ... else ...。这种代码后期会非常难维护。
第四层是 Vendor / Plugin Layer。
这一层才是真正承载设备功能的地方。你项目里已经有 V1V2Vendor、V3Vendor 以及对应的 plugin 体系,这说明它不是按“一个命令一个类”在组织,而是在按能力模块组织。这个方向是对的,因为耳机类项目的命令并不是孤立存在的。电量、ANC、EQ、设备信息、升级,这些都更适合作为模块管理,而不是散成一堆 util。
第五层是 Business Layer。
也就是 ViewModel、UI、设置页、升级页这些面向用户的逻辑。它最不应该知道的,就是底层 packet 到底长什么样、当前 characteristic 是哪个 UUID、当前 GAIA 版本该怎么判断。业务层应该拿到的是“设备支持哪些能力”“当前状态是什么”“我能不能发这个操作”。
换句话说,业务层永远不该直接依赖协议版本判断。
这件事说起来很抽象,但在项目里其实特别具体。比如一个“读取电量”的需求,如果没有协议分层,最后就很容易变成这样:
if (gaiaVersion == 3) {
// send v3 packet
} else {
// send v1/v2 packet
}
看起来就两行判断,好像没什么问题。但这种写法只要一多,版本逻辑就开始污染业务层。后面再加一个“电池盒状态”“双耳电量”“佩戴状态”,代码只会越来越散。
更合理的思路是把差异留在版本层或者 plugin 层,业务层只拿统一接口:
interface BatteryFeature {
fun requestBattery()
}
然后由不同版本实现各自适配:
class BatteryFeatureV3(...) : BatteryFeature {
override fun requestBattery() {
// send v3 packet
}
}
class BatteryFeatureV1V2(...) : BatteryFeature {
override fun requestBattery() {
// send v1/v2 packet
}
}
最后由版本路由层决定给业务层注入哪个实现。
这样写的好处很明显。业务层不会被协议版本污染,版本差异被收敛在更靠下的地方,后面加设备、加功能、加版本时,扩展成本会低很多。
很多 GAIA 项目到后期不好维护,本质上都不是因为协议太复杂,而是因为一开始没把“版本共存”当成架构问题,而只是把它当成“多做几个兼容判断”。
这两个思路的差别非常大。
如果你把版本共存当成兼容判断,最终代码一定会往四处扩散。
如果你把版本共存当成协议分层问题,很多复杂度其实会被收敛起来。
除了版本层,插件初始化顺序也是这类项目里很重要的一层。
你现在项目日志里还有这种状态:
basicPluginReady=false, upgradePluginReady=false
这说明系统并没有把“连接成功”直接等同于“所有协议能力都 ready 了”。这是对的。
因为在多版本共存场景下,不同插件很可能依赖不同的前置条件:
-
设备版本是否识别完成
-
对应 vendor 是否已接管
-
基础设备信息是否同步完成
-
升级功能是否完成初始化
如果没有这些状态边界,业务层就很容易在错误的时间点发命令,最后看起来像“设备没响应”,其实只是协议系统还没准备好。
所以协议分层里还有一个容易被忽略的原则:
ready 也是分层的一部分。
不是只有连接状态才需要状态机,协议版本、插件能力、升级能力也都应该有自己的 ready 状态。
这一点对 OTA 尤其重要。因为 OTA 一旦介入,系统就不只是“普通控制协议”了,而是要进入一个更严格的升级上下文。你项目日志里已经能看到 UpgradeFail,这恰恰说明 OTA 并不是一个孤立功能,而是建立在版本识别、插件初始化、协议发送、连接稳定性之上的一条复合链路。
也正因为如此,GAIA v1/v2/v3 共存的 Android 端设计,最忌讳的是直接把 OTA 也揉进普通命令通道逻辑里。升级相关能力最好还是作为一个相对独立的 plugin 或模块,在统一的版本路由和连接状态之上工作,而不是四散在业务代码里。
如果把今天这篇文章压成一句更直白的话,我会这样说:
GAIA 多版本共存时,Android 端真正该做的不是“把所有版本都支持一遍”,而是“把不同版本的复杂度隔离到合适的层里”。
BLE 负责通道,Reader / Sender 负责收发,Version Router 负责判版本,Vendor / Plugin 负责功能实现,业务层只消费统一能力。
这个边界一旦清楚,项目虽然还是复杂,但复杂得有秩序。
反过来,如果这些层混在一起,哪怕一开始能跑,后面设备一多,协议一变,整个工程很快就会被版本差异拖垮。
高通耳机 App 这类项目为什么不好写,很多时候不是因为某个命令太难,而是因为一旦进入多版本共存阶段,Android 端实际上已经不再是在写“蓝牙功能”,而是在写一套长期可演进的协议框架。