← 全部文章 嵌入式技术

在原厂 SDK 之上立一层业务框架:结构、纪律与收益

拿到一套原厂蓝牙音频 SDK,第一条分岔路就摆在面前:需求改动是直接写进原厂源码,还是在它上面再包一层自己的业务框架。多数团队一开始都选前者——快。半年后再回头看,代价通常出现在两个时刻:原厂发新版本的时候,和要给第二个产品出固件的时候。

这篇讲这层框架具体怎么落地(六个动作)、它能换回什么,以及它最容易失败在哪。

一、先看清原厂 SDK 的三种"不能碰"

原厂会发新版本。 改动写进原厂文件里,下一个版本就是一场逐文件的手工合并。合并质量取决于当时的疲劳程度,而不是工程判断。

预编译库改不了。 平台、协议栈、编解码这类核心通常以静态库形式提供,只留一层公开接口头。库里没有源码,想改行为只能靠它预留的弱符号覆盖点——“改库内部"这条路本来就不存在。

主链路太脆。 配对、回连、主从切换、入仓出仓这类链路时序约束密集,它们不是普通功能,而是一组互相咬合的约定。新功能插进去,问题往往几天后才以"偶发"的形式出现。

三条合起来指向同一个做法:原厂源码层尽量只留 guard——一个判断、一次转发,把控制权交出去。

二、框架落地的六个动作

1. 划"所有权域”,而不只是分目录

(图 1)

图 1 · 业务层、guard 层与原厂 SDK

图 1 · 业务层、guard 层与原厂 SDK

很多团队做了分层,收益却有限,因为只分了目录,没分所有权。目录解决文件放哪,所有权解决"这类状态由谁说了算"。实践里值得先划出来的是六个域:

  • 连接域:谁接管蓝牙初始化与模式重入,别人只能请求,不能直接改状态;
  • 媒体域:按键、App 命令、自动播放,全部收敛到同一个命令入口;
  • 提示音域:队列与播放反馈只归一处,避免两段代码同时抢播;
  • 存储域:升级与日志这类要写同一块分区的,必须显式交接;
  • 效果域:降噪、均衡这类运行态,谁下发、谁保存、谁上报,边界要写死;
  • 协议域:自家 App、客户 App、平台协议,挂同一套生命周期。

2. 原厂源码层只留 guard

每个接入点只保留"判断 + 转发",真正的逻辑在业务层。这条纪律的价值在半年后才显现——原厂升级时,你要重放的只是这些薄薄的接入点。

3. 唯一控制框架 + 冷启动分发表

模式切换、状态恢复这类全局动作,最容易演化成"到处都改一点"。可行做法是立一个统一控制框架:注册所有业务处理器,冷启动时由一张分发表决定谁接手。每个状态只有一个真实所有者,别的模块只能发请求。

这一步的收益不是代码变少,而是问题的表述方式变了:以前要问"这块状态谁改的",现在要问"哪个所有者没按契约走"。对偶发问题,这几乎是可查与不可查的分界。

4. 持久化先定契约,再写代码

业务要存的东西(设备身份、配对信息、用户偏好、校准值)如果散着写,很快就会出现"升级后数据错位"。值得当契约来做的有四条:

  • 业务独占一块固定存储区,不复用原厂系统区;
  • 每个字段用固定偏移 + 显式长度表,禁止用结构体 sizeof 推导地址;
  • 新增字段只在尾部追加,老数据仍可读;
  • 写接口收敛成一个(读、写、是否立即落盘由参数决定),不允许各模块自己刷 flash。

最后一条尤其重要:多一处直接写 flash,就多一处掉电写坏的机会。

5. 双耳私有数据走一条明确的通道

双耳产品必然要在主耳和从耳之间同步业务数据(用户设置、状态快照、电量、模式)。如果每加一个功能就临时塞一段同步代码,很快会变成"谁能同步、同步什么"都说不清。

可行做法是收敛成一条私有通道:字段化定义,每个字段有编号和长度,主耳写入、从耳读取,带有效性标志。新增字段只是加一个编号,不改变既有字段语义。

6. 门禁宏分两层

模块级开关(这部分功能要不要编进固件)和业务级开关(这个产品档位要不要这个业务)分开。这样同一套业务代码可以裁剪出不同档位的固件,去掉功能时去掉的是编译单元,而不是散落各处的 #if。

三、它能换回什么

(图 2)

图 2 · 加一层前后的长期成本对照

图 2 · 加一层前后的长期成本对照

把上面六件事做到位,换回的是六件具体的东西:

1. 可升级。 原厂换版本、甚至换芯片平台,业务层是资产而不是负债,要重做的只是 guard。

2. 可裁剪。 两层门禁让同一套业务代码支撑多个产品档位,产测代码隔离在宏后面,量产固件里根本不存在。

3. 可定位。 每类状态唯一所有者,配合统一日志等级,日志能追到一个明确入口。

4. 可兼容。 固定偏移 + 显式长度表的持久化方案,让 OTA 升级不需要数据迁移,也不会因为结构体变更而错位。

5. 可共存。 多套协议挂在同一套生命周期上:如果各自维护状态机,组合数是乘法;共用生命周期,组合数是加法。

6. 可回归。 新功能的改动面被限制在业务层,主链路(配对、回连、主从切换、入出仓)几乎不受影响——而这几条链路恰恰是最不该被碰的。

四、最容易失败的三种形态

第一种:分层了,但没分权。 目录建好了,状态还是谁都能改。结果只是把散代码换了个地方放,收益为零。

第二种:guard 越写越厚。 一开始是"一个判断 + 一次转发",后来变成"顺手在这里也改一下"。半年后原厂文件里又长满了业务逻辑,合并成本回到原点。

第三种:契约没有文档。 固定偏移、字段编号、门禁宏这些约定如果只存在于作者脑子里,下一个人接手时会用最省事的方式绕开它。

这三种的共同点是:框架的价值不来自目录结构,来自纪律。纪律需要文档和评审来维持,否则它会自然衰减。

五、什么时候不该加

  • 一次性项目:产品生命周期短,没有第二个产品要复用;
  • 单功能设备:只有一个工作模式,没有状态竞争要仲裁;
  • 长期只有一个人维护且没有升级压力:此时"薄"比"整齐"更值钱。

一句话:当"原厂升级"和"第二个产品"在你的路线图上是确定的,这层的收益就确定;两件都不确定,先别加。

一张检查清单

  • 原厂源码层里,你留下的是 guard,还是业务逻辑?
  • 每一类共享状态都有唯一所有者吗?能指出具体是哪个入口吗?
  • 持久化是固定偏移表还是 sizeof 推导?新增字段会不会顶偏老数据?
  • 双耳同步有明确通道和字段编号,还是各处临时拼的?
  • 模块开关和业务开关分开了吗?
  • 如果明天原厂发一个新版本,你要改多少行业务代码?

最后一问最值得常问——它一次性把分层的收益和债务都算了出来。