EVCC AIDL / 米德宝联动 v2 协议¶
最后更新:
适用于外部应用通过 EVCC 导出的 Binder Service 读取联动快照,以及经用户授权后写入车辆属性。
当前协议版本:
2最后按源码核对:2026-08-27
历史属性协议参考:EVCC 3.1.3 的能力版本已升为
3,下述原六个方法和属性快照version: 2保持兼容。 新增音乐通道见 v3 协议与独立音乐接口定义。
1. 文档范围¶
本文档描述:
- EVCC 如何对外暴露米德宝联动 Binder Service;
- 两个 AIDL 接口及其 Binder ABI;
- 银河悬浮歌词与自定义应用的绑定、握手、初始同步和回调流程;
- AIDL 数据配置、导入、导出、开放读取和多应用写入授权;
- JSON 快照的字段、类型和兼容行为;
- 推送时机、断线恢复、读写权限边界和协议升级约束;
- 当前实现已知的边界和风险。
本文档不把以下两条链路视为同一协议:
- 车机厂商的
ecarxcar_service/car_signalAIDL。它是车辆属性进入 EVCC 的数据源之一。 - 米德宝 BLE 直发模式。该模式使用 BLE 广播、GATT 或 MIBO 离线转发,不使用本文的 JSON 属性快照协议。
2. 角色与端点¶
| 角色 | 应用 | 包名 | 职责 |
|---|---|---|---|
| 服务端 | EVCC | com.kooo.evcc |
采集属性、生成快照、校验写入授权并提供 Binder Service |
| 米德宝客户端 | 银河悬浮歌词 | com.galaxy.lyricsnext |
在“米德宝 AIDL 联动”开启时接收快照 |
| 自定义读取客户端 | 任意外部应用 | 不限制 | 在“旧版 AIDL 联动”开启时读取状态、快照并注册回调 |
| 授权写入客户端 | 用户在 EVCC 中勾选的应用 | 多选 | 包名和安装时固定的签名均匹配后,可调用属性写入方法 |
EVCC Service:
Class: com.kooo.evcc.midebao.MidebaoLinkBridgeService
Action: com.kooo.evcc.midebao.BIND
Exported: true
Manifest 源码:app/src/main/AndroidManifest.xml
客户端使用带包名限定的 Action 绑定:
Intent intent = new Intent("com.kooo.evcc.midebao.BIND");
intent.setPackage("com.kooo.evcc");
bindService(intent, connection, Context.BIND_AUTO_CREATE);
3. 整体数据链路¶
flowchart LR
A["VHAL / APVP / 车机 AIDL / CAPI / AdaptAPI / SOA"] --> B["EVCC 统一 Property"]
V["EVCC 自动化变量"] --> C["米德宝项目匹配"]
B --> C
C --> D["提取 rawValue / map 映射 / 去重"]
D --> E["v2 全量 JSON 快照"]
E --> F["IMidebaoLinkBridge"]
F --> G["开放读取客户端"]
G --> H["数据面板 / 规则判断 / 客户端自有动作"]
W["用户授权写入 App"] -->|"writeProperty"| F
F --> I["VHAL / APVP / AIDL / CAPI / AdaptAPI / SOA 写入"]
如果某个属性恰好来自车机 AIDL,整体会经过两次 Binder 跨界:
车机 ecarxcar_service
-> EVCC AidlManager
-> EVCC 统一 Property
-> MidebaoLinkageManager
-> EVCC IMidebaoLinkBridge
-> 外部读取客户端
其他属性源只会经过最后一次跨应用 AIDL。
4. AIDL 接口定义¶
4.1 主接口¶
客户端通过主接口读取 EVCC 状态、注册回调;获得写入授权的客户端还可写入车辆属性。
package com.kooo.evcc.midebao;
import com.kooo.evcc.midebao.IMidebaoLinkCallback;
interface IMidebaoLinkBridge {
int getProtocolVersion();
String getStatus();
String getSnapshotJson();
void registerCallback(IMidebaoLinkCallback callback);
void unregisterCallback(IMidebaoLinkCallback callback);
boolean writeProperty(int propertyId, int areaId, String value);
}
源码:shared/src/main/aidl/com/kooo/evcc/midebao/IMidebaoLinkBridge.aidl
4.2 回调接口¶
EVCC 通过回调接口主动通知客户端。
package com.kooo.evcc.midebao;
oneway interface IMidebaoLinkCallback {
void onSnapshotChanged(String snapshotJson);
void onStatusChanged(String status);
}
源码:shared/src/main/aidl/com/kooo/evcc/midebao/IMidebaoLinkCallback.aidl
EVCC 与每个客户端保存的 .aidl 必须保持一致。
5. Binder ABI¶
5.1 主接口 ABI¶
接口描述符:
| 方法 | Transaction code | 调用方式 | Parcel 输入 | Parcel 返回 |
|---|---|---|---|---|
getProtocolVersion() |
FIRST_CALL_TRANSACTION + 0 |
同步 | 无 | int |
getStatus() |
+1 |
同步 | 无 | String |
getSnapshotJson() |
+2 |
同步 | 无 | String |
registerCallback(callback) |
+3 |
同步 | Callback Binder | 无 |
unregisterCallback(callback) |
+4 |
同步 | Callback Binder | 无 |
writeProperty(propertyId, areaId, value) |
+5 |
同步 | int, int, String |
boolean |
所有主接口方法均使用普通同步 transact(..., flags = 0):
- Proxy 创建请求 Parcel 和回复 Parcel;
- 请求 Parcel 首先写入接口描述符;
- Stub 使用
enforceInterface()校验描述符; - 成功时 Stub 写入
writeNoException()和返回值; SecurityException和RemoteException可通过 Binder 回传。
5.2 回调接口 ABI¶
回调接口描述符:
| 方法 | Transaction code | 调用方式 | Parcel 输入 |
|---|---|---|---|
onSnapshotChanged(snapshotJson) |
FIRST_CALL_TRANSACTION + 0 |
FLAG_ONEWAY |
String |
onStatusChanged(status) |
+1 |
FLAG_ONEWAY |
String |
oneway 含义:
- EVCC 只写请求 Parcel,不创建回复 Parcel;
- EVCC 不等待客户端完成 JSON 解析或规则执行;
- 协议层没有 ACK,单次回调失败不会重发该消息。
6. 绑定与握手时序¶
sequenceDiagram
participant G as "AIDL 读取客户端"
participant A as "Android Service"
participant E as "EVCC Bridge"
G->>A: bindService(action + package)
A->>E: onCreate / onBind
E-->>G: IBinder
G->>E: getProtocolVersion()
E-->>G: 2
G->>E: registerCallback(callback Binder)
E--)G: onStatusChanged(currentStatus)
E--)G: onSnapshotChanged(currentSnapshot)
E-->>G: registerCallback 返回
G->>E: getSnapshotJson()
E-->>G: 当前全量 JSON
loop 属性或变量发生变化
E--)G: onSnapshotChanged(fullSnapshot)
end
opt 用户已授权该 App 写入
G->>E: writeProperty(propertyId, areaId, value)
E-->>G: true / false
end
银河悬浮歌词参考客户端当前的具体顺序是:
- 将
IBinder转换为IMidebaoLinkBridge; - 调用
getProtocolVersion(); - 调用
registerCallback(); - 调用
getSnapshotJson()主动拉取当前快照; - 之后依赖
onSnapshotChanged()接收变化。
registerCallback() 内部会立即发送一次状态和快照,客户端随后又主动拉取一次。因此首次连接通常会处理两份内容相同或接近的快照。
其他客户端可以只依赖 registerCallback() 的首次推送,也可以在注册后再调用 getSnapshotJson()。无论采用哪种方式,都应把快照当作全量状态并允许重复处理。
7. 主接口方法语义¶
7.1 getProtocolVersion()¶
当前固定返回:
它表示跨应用快照协议版本,不是 EVCC 应用版本,也不是车机 AIDL 版本。
旧版读取客户端只把该值写入日志,尚未强制验证 version == 1;由于 v2 只在接口末尾追加方法,原有五个方法的 transaction code 保持不变。
7.2 getStatus()¶
返回人类可读的运行状态,例如:
该字段是用户可读文本,不是稳定枚举。客户端不应通过匹配具体中文来决定业务逻辑。
7.3 getSnapshotJson()¶
返回 EVCC 内存中最新的一份全量快照。
该方法:
- 不会因为本次调用而强制刷新车辆属性;
- 不等待上游属性源完成连接;
- 适用于首次同步、断线恢复和调试查询。
7.4 registerCallback(callback)¶
服务端将 Callback Binder 注册到 RemoteCallbackList,然后立即调用:
onStatusChanged(statusText);onSnapshotChanged(snapshotJson)。
RemoteCallbackList 可在客户端 Binder 死亡后清理失效回调。
7.5 unregisterCallback(callback)¶
客户端正常停止时主动注销。如果客户端异常退出,Binder 死亡后也会由 RemoteCallbackList 清理。
7.6 writeProperty(propertyId, areaId, value)¶
仅用户在 EVCC 的“允许写入的应用”列表中勾选并保存的 App 可以调用。参数语义:
propertyId:目标车辆属性 ID,必须为正整数;areaId:目标区域 ID,不得小于0;value:按目标属性数据类型解析的原始字符串,最大 4096 个字符。
EVCC 按以下优先级寻找精确匹配的 propertyId + areaId:
一旦某个来源命中,该来源的执行结果就是最终结果;写入失败后不会继续向下一来源重复下发。成功返回 true,参数无效、未找到属性、数据源拒绝、解析失败、异常或 5 秒超时均返回 false。未通过写入鉴权时不是返回 false,而是抛出 SecurityException。
该方法不限制目标必须出现在 AIDL 快照配置中。给某个 App 授权,意味着允许它尝试写入 EVCC 当前数据源能够识别的任意车辆属性,因此只应选择明确可信且确有写入需求的 App。
服务端入口:app/src/main/java/com/kooo/evcc/midebao/MidebaoLinkBridgeService.kt;快照与回调:app/src/main/java/com/kooo/evcc/midebao/MidebaoLinkageManager.kt
8. JSON 快照协议¶
8.1 正常运行快照¶
{
"version": 2,
"enabled": true,
"updatedAt": 1786075800000,
"items": [
{
"key": "gear",
"name": "档位",
"hasValue": true,
"value": "D",
"rawValue": "3",
"updatedAt": 1786075799123
},
{
"key": "speed",
"name": "车速",
"hasValue": true,
"value": "52",
"rawValue": "52",
"updatedAt": 1786075799456
}
]
}
8.2 停用快照¶
8.3 顶层字段¶
| 字段 | 类型 | 必需 | 语义 |
|---|---|---|---|
version |
int |
是 | 快照协议版本,当前为 2 |
enabled |
boolean |
是 | 属性联动的启用状态 |
status |
string |
否 | 停用或状态说明 |
updatedAt |
long |
是 | 本次快照生成时间,Unix 毫秒 |
items |
array |
是 | 当前所有已配置项目的完整列表 |
8.4 项目字段¶
| 字段 | 类型 | 必需 | 语义 |
|---|---|---|---|
key |
string |
是 | 稳定且唯一的业务标识,客户端规则依赖该值 |
name |
string |
是 | 用户可见名称 |
hasValue |
boolean |
是 | 是否已经成功读取有效值 |
value |
JSON 值 | 是 | 经过 map 映射后的值 |
rawValue |
string |
是 | 映射前的原始字符串 |
updatedAt |
long |
是 | 该项目最后变化时间,未取到值时为 0 |
未取到值的项目:
value 通常为字符串,但映射 JSON 也可使其成为数字或布尔值。当前客户端最终通过 String.valueOf() 将其统一转成字符串。新配置应将 value 限制为标量值,不应在其中嵌套对象或数组。
9. 客户端宽松解析行为¶
当前客户端使用 JSONObject.opt*() 宽松解析:
- 缺少
enabled:按false处理; - 缺少
items:按空数组处理; key缺失或为空:丢弃该项目;name缺失或为空:使用key;value和rawValue:统一转成字符串;- 缺少
hasValue:根据updatedAt和值是否为空推断; - 额外未知字段:忽略;
- 顶层
version:当前未强制验证。
因此,JSON 格式适合通过“只追加新字段”的方式扩展,不适合直接修改已有字段的类型或语义。
10. 值处理与推送时机¶
10.1 属性匹配¶
属性项目按下列条件精确匹配:
匹配后的处理顺序:
- 提取
rawValue; - 通过
map[rawValue]查找映射值; - 未命中映射时使用原值;
- 与内存中上一个原值和映射值比较;
- 值未改变时不生成新快照。
10.2 推送策略¶
realtime=true:变化后立即生成并推送快照;realtime=false:300 毫秒内的多次变化合并为一次推送;- 自动化变量:事件监听之外,每 1 秒补查一次;
- 初始值未读取成功:前 60 秒每 2 秒重试,之后每 10 秒重试;
- 每次
onSnapshotChanged()都传输完整items数组,不是单属性增量。
10.3 AIDL 数据配置¶
EVCC 中“米德宝 AIDL 联动”和“旧版 AIDL 联动”共用同一份项目配置。顶层格式:
{
"version": 1,
"items": [
{
"key": "speed",
"name": "车速",
"propertyId": 557849089,
"areaId": 0,
"realtime": true
},
{
"key": "music_bpm",
"name": "音乐 BPM",
"variable": "bpm",
"map": {
"0": "未识别"
},
"realtime": true
}
]
}
这里的配置文件 version 仍为 1,它只表示本地项目配置结构;Binder 接口和运行时快照的协议版本为 2。两者独立升级,客户端不要混用。
每个项目必须满足:
key非空且在整份配置中唯一;客户端规则应依赖key,不要依赖显示名;name非空,用于界面显示;propertyId和variable必须且只能填写一个;propertyId可为 JSON 数字、十进制字符串或0x十六进制字符串;运行时统一解析为 32 位正整数,使用表单编辑新项目时会写成十进制数字,直接导入的原始 JSON 则会按原字段形式保存并导出;areaId仅用于属性,省略时为0;variable使用不带$的 EVCC 变量名,可读取自定义、时间、GPS、bpm和music_state;map可选,键按原始字符串精确匹配,值应使用字符串、数字、布尔或null等标量;realtime省略时为false。
配置至少需要一个有效项目。导入文件最大为 8 MiB;EVCC 会在校验后去除格式空白,去除后的有效配置最大为 5 MiB。支持带 UTF-8 BOM 的 JSON;校验失败时不会覆盖当前配置。导出文件写入:
“恢复默认”会按当前车型读取云端车型配置中的 AIDL 数据默认值;车型没有配置时使用 EVCC 内置示例。
11. 连接生命周期与恢复¶
客户端遇到以下事件时会清除当前 Bridge 并进入重连:
onServiceDisconnected();onBindingDied();onNullBinding();- 握手方法抛出异常。
当前重连间隔:
协议没有持久化消息队列或离线重放。断线期间的中间变化不会逐条补发;恢复连接后,客户端直接获得当前最新的全量状态。
12. 读取开放与写入鉴权¶
Service 为 exported=true。v2 把读取和写入权限彻底分开。
12.1 读取权限¶
- 总开关和“旧版 AIDL 联动”同时开启时,
getProtocolVersion()、getStatus()、getSnapshotJson()、registerCallback()和unregisterCallback()不校验调用 App 的包名或签名,任何能绑定 Service 的 App 均可读取; - “旧版 AIDL 联动”关闭时,开放读取立即停止;如果“米德宝 AIDL 联动”仍开启,则只允许包名和证书都匹配的银河悬浮歌词继续读取;
- 总开关关闭时,两种读取出口都停止。
因此,自定义读取快照中不应配置不希望其他车机 App 获知的数据。开放读取只影响上述五个读取/订阅方法,不会自动获得属性写入能力。
12.2 写入权限¶
普通旧版外部应用写入必须同时满足:
- 总开关已开启;
- “旧版 AIDL 联动”已开启;
- 用户在“允许写入的应用”弹窗中勾选了该 App;
- 当前 Binder UID 对应的包名与保存时固定的 APK 签名 SHA-256 均匹配。
App 选择支持多选。保存时 EVCC 读取每个已安装 App 的签名并与包名一起固定;空名单表示普通旧版外部 App 均为只读。旧版本中曾用于读取的调用方不会自动迁移为可写 App,升级后仍需用户主动选择。
当前官方银河通道另有包名和签名校验的属性写入规则:开启总开关和“米德宝 AIDL 联动”后可写入,不依赖普通旧版应用名单。新增音乐权限独立于属性读写权限,仍仅向身份明确的银河开放。
每次 writeProperty() 调用的验证流程:
Binder.getCallingUid()
-> 枚举 UID 对应包名
-> 在用户保存的多应用写入名单中查找包名
-> 读取目标 APK 当前签名
-> 计算并规范化 SHA-256
-> 任一实际摘要命中该包名允许列表才放行
未通过验证时抛出 SecurityException,不会进入车辆属性写入链路。
鉴权实现:app/src/main/java/com/kooo/evcc/midebao/MidebaoLinkBridgeService.kt
银河悬浮歌词通道的允许摘要可通过车辆适配配置更新;自定义写入名单由用户在 EVCC 界面管理。不要把具体证书摘要写进公开业务 payload 或当作协议字段传给客户端。
需要注意:
bindService()本身不执行读取或写入鉴权;- 自定义读取开放时,调用读取方法和注册回调都不要求授权;
- 写入名单只保护
writeProperty(),不会限制已开放的读取; - 关闭自定义 AIDL 后,之前开放注册的回调不再接收后续推送;
- App 卸载后换证书重装,即使包名相同也不能沿用原写入授权。
13. 协议不提供的能力¶
当前 AIDL 不包含:
- 调用 EVCC 自动化动作;
- 反向发送机器人指令;
- 写入 EVCC 变量或修改 AIDL 数据配置;
- 属性元数据,如
propertyId、areaId、数据类型和访问权限; - 属性来源,如 VHAL、APVP 或 AIDL;
- 消息序号、ACK、重放、分片和压缩。
客户端收到快照后,可在本地根据 key 和 value 展示或评估规则;如需写入车辆属性,必须使用 v2 的 writeProperty() 并由用户授权。其他后续动作仍由客户端自己的能力执行。
14. 协议升级约束¶
本接口是普通应用 AIDL,不是 Android Stable AIDL,没有 @VintfStability 或系统级 ABI 稳定保证。
修改时必须遵守:
- 不得调整现有 AIDL 方法顺序;
- 不得删除或修改现有方法的参数和返回类型;
- 新方法只能追加到接口末尾;
- EVCC 和客户端必须同步同一份
.aidl; - 不兼容变更必须提升
PROTOCOL_VERSION; - JSON 新能力应优先通过追加可选字段实现;
- 保留
key/name/hasValue/value/rawValue/updatedAt的现有语义。
由于旧版客户端只记录版本号而不拒绝未知版本,v2 保留了原有读取方法顺序和 JSON 字段语义。新客户端仍应增加明确的版本范围检查,并且只有 getProtocolVersion() >= 2 时才调用 writeProperty()。
15. 当前已知边界¶
15.1 属性配置不能指定来源¶
配置只有 propertyId 和 areaId,没有 source。填写某个属性 ID 不代表强制从车机 AIDL 读取。
初始主动读取优先级:
实时阶段同时监听五个来源。如果不同来源存在相同的 propertyId + areaId,任何来源都可能更新同一个快照项目。
读取逻辑:app/src/main/java/com/kooo/evcc/miniprogram/MiniProgramPropertyHelper.kt
15.2 写入不能指定来源¶
writeProperty() 同样没有 source 参数,并按固定优先级寻找精确匹配的属性。一旦命中某个来源,即使写入失败也不会回退到下一来源。这能避免同一指令被多个数据源重复执行,但调用端无法用该接口强制选择某个来源。
写入逻辑:app/src/main/java/com/kooo/evcc/miniprogram/MiniProgramPropertyHelper.kt
15.3 没有快照序号¶
协议只有时间戳,没有单调递增的 sequence。客户端当前也不会根据顶层 updatedAt 丢弃过期快照。
由于首次注册时同时存在回调推送和主动拉取,若握手期间恰好发生属性更新,理论上可能短暂交错处理新旧快照。
后续可在顶层增加单调 sequence,客户端忽略不大于当前序号的快照。
15.4 协议版本未强制检查¶
getProtocolVersion() 和 JSON 中的 version 均存在,但旧版客户端没有拒绝未知版本。它们目前主要用于日志和人工识别;v2 写入客户端必须主动检查版本。
15.5 配置错误时 enabled 语义不严谨¶
当配置解析失败时,当前实现会:
- 停止属性监听并将内部
running设为false; - 通过状态回调发送“配置错误”;
- 但生成的 JSON 快照仍可能是
enabled: true。
因此客户端不应仅依赖 enabled 判断链路健康度。后续宜将配置错误快照改为 enabled: false,或增加稳定的机器可读 state/errorCode。
15.6 多种联动方式独立运行¶
“米德宝 BLE 直发联动”“米德宝 AIDL 联动”和“旧版 AIDL 联动”是三个独立子开关:
- BLE 直发和 AIDL 可以同时开启;
- 米德宝 AIDL 与自定义 AIDL 可以同时开启;此时自定义出口开放读取同一份快照;
- 关闭某个子开关只停止对应出口,不应被理解为清空公共 AIDL 数据配置;
- 总开关关闭时所有联动出口一起停止。
运行调度:app/src/main/java/com/kooo/evcc/midebao/MidebaoLinkageRuntime.kt
15.7 无分片与流量控制¶
快照使用单个 Binder String 传输,没有分片、压缩和背压。协议只适合少量车辆信号,不应用来传输大量历史数据或二进制内容。
16. 后续协议改进方向¶
如果后续继续升级协议,建议优先考虑:
- 顶层增加单调递增的
sequence; - 增加机器可读的
state和errorCode; - 客户端明确校验支持的协议版本范围;
- 将首次同步收敛为一条明确路径,避免回调与主动拉取重复;
- 如需强制属性数据源,在 EVCC 配置端增加
source,但是否对外传输需单独评估; - 修正配置错误时的
enabled/ 运行状态语义; - 如项目数量显著增长,再评估增量快照或更紧凑的 Parcelable 协议。
17. 源码索引¶
| 内容 | 文件 |
|---|---|
| 主 AIDL | shared/src/main/aidl/com/kooo/evcc/midebao/IMidebaoLinkBridge.aidl |
| 回调 AIDL | shared/src/main/aidl/com/kooo/evcc/midebao/IMidebaoLinkCallback.aidl |
| Binder Service | app/src/main/java/com/kooo/evcc/midebao/MidebaoLinkBridgeService.kt |
| 快照生成与回调 | app/src/main/java/com/kooo/evcc/midebao/MidebaoLinkageManager.kt |
| 协议版本与项目配置 | app/src/main/java/com/kooo/evcc/midebao/MidebaoLinkageConfig.kt |
| 多出口运行调度 | app/src/main/java/com/kooo/evcc/midebao/MidebaoLinkageRuntime.kt |
| 多数据源属性读取与写入 | app/src/main/java/com/kooo/evcc/miniprogram/MiniProgramPropertyHelper.kt |
| AIDL 配置管理、导入与导出 | app/src/main/java/com/kooo/evcc/midebao/MidebaoLinkageCardController.kt |
银河悬浮歌词参考实现位于 Galaxy-Robot-Midebao-Car-Machine-Linkage 仓库的: