跳转至

EVCC AIDL / 米德宝联动 v1 协议

最后更新:

适用于受信任应用通过 EVCC 导出的 Binder Service 订阅车辆属性和 EVCC 变量。

当前协议版本:1

最后按源码核对:2026-08-27

1. 文档范围

本文档描述:

  • EVCC 如何对外暴露米德宝联动 Binder Service;
  • 两个 AIDL 接口及其 Binder ABI;
  • 银河悬浮歌词、仪表 DIY、AutoDisplay 与自定义受信任应用的绑定、握手、初始同步和回调流程;
  • AIDL 数据配置、导入、导出和多调用方开关;
  • JSON 快照的字段、类型和兼容行为;
  • 推送时机、断线恢复、调用方鉴权和协议升级约束;
  • 当前实现已知的边界和风险。

本文档不把以下两条链路视为同一协议:

  1. 车机厂商的 ecarxcar_service / car_signal AIDL。它是车辆属性进入 EVCC 的数据源之一。
  2. 米德宝 BLE 直发模式。该模式使用 BLE 广播、GATT 或 MIBO 离线转发,不使用本文的 JSON 属性快照协议。

2. 角色与端点

角色 应用 包名 职责
服务端 EVCC com.kooo.evcc 采集属性、生成快照、提供 Binder Service
米德宝客户端 银河悬浮歌词 com.galaxy.lyricsnext 在“米德宝 AIDL 联动”开启时接收快照
内置自定义客户端 仪表 DIY com.kooo.instrumentdiy 在“旧版 AIDL 联动”开启时接收快照
内置自定义客户端 AutoDisplay com.simplerbit.autodisplay 在“旧版 AIDL 联动”开启时接收快照
可配置客户端 用户指定应用 管理页配置 在“旧版 AIDL 联动”开启时按包名和签名接收快照

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["v1 全量 JSON 快照"]
    E --> F["IMidebaoLinkBridge"]
    F --> G["受信任 AIDL 客户端"]
    G --> H["数据面板 / 规则判断 / 客户端自有动作"]

如果某个属性恰好来自车机 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);
}

源码: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

接口描述符:

com.kooo.evcc.midebao.IMidebaoLinkBridge
方法 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 无

所有主接口方法均使用普通同步 transact(..., flags = 0):

  • Proxy 创建请求 Parcel 和回复 Parcel;
  • 请求 Parcel 首先写入接口描述符;
  • Stub 使用 enforceInterface() 校验描述符;
  • 成功时 Stub 写入 writeNoException() 和返回值;
  • SecurityException 和 RemoteException 可通过 Binder 回传。

5.2 回调接口 ABI

回调接口描述符:

com.kooo.evcc.midebao.IMidebaoLinkCallback
方法 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: 1

    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

银河悬浮歌词参考客户端当前的具体顺序是:

  1. 将 IBinder 转换为 IMidebaoLinkBridge;
  2. 调用 getProtocolVersion();
  3. 调用 registerCallback();
  4. 调用 getSnapshotJson() 主动拉取当前快照;
  5. 之后依赖 onSnapshotChanged() 接收变化。

registerCallback() 内部会立即发送一次状态和快照,客户端随后又主动拉取一次。因此首次连接通常会处理两份内容相同或接近的快照。

其他客户端可以只依赖 registerCallback() 的首次推送,也可以在注册后再调用 getSnapshotJson()。无论采用哪种方式,都应把快照当作全量状态并允许重复处理。

7. 主接口方法语义

7.1 getProtocolVersion()

当前固定返回:

1

它表示跨应用快照协议版本,不是 EVCC 应用版本,也不是车机 AIDL 版本。

当前客户端只把该值写入日志,尚未强制验证 version == 1。

7.2 getStatus()

返回人类可读的运行状态,例如:

未启用
运行中,已加载 3 个项目
配置错误: ...

该字段是用户可读文本,不是稳定枚举。客户端不应通过匹配具体中文来决定业务逻辑。

7.3 getSnapshotJson()

返回 EVCC 内存中最新的一份全量快照。

该方法:

  • 不会因为本次调用而强制刷新车辆属性;
  • 不等待上游属性源完成连接;
  • 适用于首次同步、断线恢复和调试查询。

7.4 registerCallback(callback)

服务端将 Callback Binder 注册到 RemoteCallbackList,然后立即调用:

  1. onStatusChanged(statusText);
  2. onSnapshotChanged(snapshotJson)。

RemoteCallbackList 可在客户端 Binder 死亡后清理失效回调。

7.5 unregisterCallback(callback)

客户端正常停止时主动注销。如果客户端异常退出,Binder 死亡后也会由 RemoteCallbackList 清理。

服务端实现:app/src/main/java/com/kooo/evcc/midebao/MidebaoLinkageManager.kt

8. JSON 快照协议

8.1 正常运行快照

{
  "version": 1,
  "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 停用快照

{
  "version": 1,
  "enabled": false,
  "status": "未启用",
  "updatedAt": 1786075800000,
  "items": []
}

8.3 顶层字段

字段 类型 必需 语义
version int 是 快照协议版本,当前为 1
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

未取到值的项目:

{
  "key": "speed",
  "name": "车速",
  "hasValue": false,
  "value": "",
  "rawValue": "",
  "updatedAt": 0
}

value 通常为字符串,但映射 JSON 也可使其成为数字或布尔值。当前客户端最终通过 String.valueOf() 将其统一转成字符串。新配置应将 value 限制为标量值,不应在其中嵌套对象或数组。

9. 客户端宽松解析行为

当前客户端使用 JSONObject.opt*() 宽松解析:

  • 缺少 enabled:按 false 处理;
  • 缺少 items:按空数组处理;
  • key 缺失或为空:丢弃该项目;
  • name 缺失或为空:使用 key;
  • value 和 rawValue:统一转成字符串;
  • 缺少 hasValue:根据 updatedAt 和值是否为空推断;
  • 额外未知字段:忽略;
  • 顶层 version:当前未强制验证。

因此,JSON 格式适合通过“只追加新字段”的方式扩展,不适合直接修改已有字段的类型或语义。

10. 值处理与推送时机

10.1 属性匹配

属性项目按下列条件精确匹配:

item.propertyId == property.id
&&
item.areaId == property.areaId

匹配后的处理顺序:

  1. 提取 rawValue;
  2. 通过 map[rawValue] 查找映射值;
  3. 未命中映射时使用原值;
  4. 与内存中上一个原值和映射值比较;
  5. 值未改变时不生成新快照。

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
    }
  ]
}

每个项目必须满足:

  • 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;校验失败时不会覆盖当前配置。导出文件写入:

Download/EVCC/exports/aidl_linkage/EVCC_AIDL_linkage_yyyyMMdd_HHmmss_SSS.json

“恢复默认”会按当前车型读取云端车型配置中的 AIDL 数据默认值;车型没有配置时使用 EVCC 内置示例。

11. 连接生命周期与恢复

客户端遇到以下事件时会清除当前 Bridge 并进入重连:

  • onServiceDisconnected();
  • onBindingDied();
  • onNullBinding();
  • 握手方法抛出异常。

当前重连间隔:

5,000 ms

协议没有持久化消息队列或离线重放。断线期间的中间变化不会逐条补发;恢复连接后,客户端直接获得当前最新的全量状态。

12. 调用方鉴权

Service 是 exported=true,但每个主接口方法都在 EVCC 内部执行调用方验证。鉴权同时受总开关和子开关约束:

  • “米德宝 AIDL 联动”只允许银河悬浮歌词及其受信任证书;
  • “旧版 AIDL 联动”允许仪表 DIY、AutoDisplay,以及最多一个用户配置的额外调用方;
  • 内置调用方各自绑定自己的证书,不共用一个全局签名白名单;
  • 额外调用方必须同时配置合法包名和至少一个 SHA-256 证书摘要。

每次方法调用的验证流程:

Binder.getCallingUid()
    -> 枚举 UID 对应包名
    -> 按当前子开关查找该包名的允许证书
    -> 读取目标 APK 当前签名或签名历史
    -> 计算并规范化 SHA-256
    -> 任一实际摘要命中该包名允许列表才放行

未通过验证时抛出 SecurityException。

鉴权实现:app/src/main/java/com/kooo/evcc/midebao/MidebaoLinkBridgeService.kt

银河悬浮歌词的允许摘要可通过车辆适配配置更新;自定义额外调用方可在 EVCC 界面配置。不要把具体证书摘要写进公开业务 payload 或当作协议字段传给客户端。

需要注意:

  • bindService() 本身不执行这组鉴权;
  • 鉴权发生在每个 Binder 方法的入口;
  • 未授权应用可能触发 Service 创建,但无法成功调用 Bridge 方法;
  • 回调在完成鉴权的 registerCallback() 之后才能加入回调列表。

13. 协议不提供的能力

当前 AIDL 不包含:

  • 写入车辆属性;
  • 调用 EVCC 自动化动作;
  • 反向发送机器人指令;
  • 属性元数据,如 propertyId、areaId、数据类型和访问权限;
  • 属性来源,如 VHAL、APVP 或 AIDL;
  • 消息序号、ACK、重放、分片和压缩。

客户端收到快照后,可在本地根据 key 和 value 展示或评估规则,再由客户端自己的能力执行后续动作。这不是反向 AIDL 调用 EVCC。

14. 协议升级约束

本接口是普通应用 AIDL,不是 Android Stable AIDL,没有 @VintfStability 或系统级 ABI 稳定保证。

修改时必须遵守:

  1. 不得调整现有 AIDL 方法顺序;
  2. 不得删除或修改现有方法的参数和返回类型;
  3. 新方法只能追加到接口末尾;
  4. EVCC 和客户端必须同步同一份 .aidl;
  5. 不兼容变更必须提升 PROTOCOL_VERSION;
  6. JSON 新能力应优先通过追加可选字段实现;
  7. 保留 key / name / hasValue / value / rawValue / updatedAt 的现有语义。

由于当前客户端只记录版本号而不拒绝未知版本,单纯将版本提升到 2 不能防止旧客户端错误解析。进行不兼容升级时,客户端应增加明确的版本范围检查。

15. 当前已知边界

15.1 属性配置不能指定来源

配置只有 propertyId 和 areaId,没有 source。填写某个属性 ID 不代表强制从车机 AIDL 读取。

初始主动读取优先级:

VHAL -> APVP -> AIDL -> CAPI -> AdaptAPI -> SOA

实时阶段同时监听五个来源。如果不同来源存在相同的 propertyId + areaId,任何来源都可能更新同一个快照项目。

读取逻辑:app/src/main/java/com/kooo/evcc/miniprogram/MiniProgramPropertyHelper.kt

15.2 没有快照序号

协议只有时间戳,没有单调递增的 sequence。客户端当前也不会根据顶层 updatedAt 丢弃过期快照。

由于首次注册时同时存在回调推送和主动拉取,若握手期间恰好发生属性更新,理论上可能短暂交错处理新旧快照。

后续可在顶层增加单调 sequence,客户端忽略不大于当前序号的快照。

15.3 协议版本未强制检查

getProtocolVersion() 和 JSON 中的 version 均存在,但当前客户端没有拒绝未知版本。它们目前主要用于日志和人工识别。

15.4 配置错误时 enabled 语义不严谨

当配置解析失败时,当前实现会:

  • 停止属性监听并将内部 running 设为 false;
  • 通过状态回调发送“配置错误”;
  • 但生成的 JSON 快照仍可能是 enabled: true。

因此客户端不应仅依赖 enabled 判断链路健康度。后续宜将配置错误快照改为 enabled: false,或增加稳定的机器可读 state/errorCode。

15.5 多种联动方式独立运行

“米德宝 BLE 直发联动”“米德宝 AIDL 联动”和“旧版 AIDL 联动”是三个独立子开关:

  • BLE 直发和 AIDL 可以同时开启;
  • 米德宝 AIDL 与自定义 AIDL 可以同时向不同受信任应用推送同一份快照;
  • 关闭某个子开关只停止对应出口,不应被理解为清空公共 AIDL 数据配置;
  • 总开关关闭时所有联动出口一起停止。

运行调度:app/src/main/java/com/kooo/evcc/midebao/MidebaoLinkageRuntime.kt

15.6 无分片与流量控制

快照使用单个 Binder String 传输,没有分片、压缩和背压。协议只适合少量车辆信号,不应用来传输大量历史数据或二进制内容。

16. 建议的 v2 改进方向

如果后续升级协议,建议优先考虑:

  1. 顶层增加单调递增的 sequence;
  2. 增加机器可读的 state 和 errorCode;
  3. 客户端明确校验支持的协议版本范围;
  4. 将首次同步收敛为一条明确路径,避免回调与主动拉取重复;
  5. 如需强制属性数据源,在 EVCC 配置端增加 source,但是否对外传输需单独评估;
  6. 修正配置错误时的 enabled / 运行状态语义;
  7. 如项目数量显著增长,再评估增量快照或更紧凑的 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 仓库的:

app/src/main/aidl/com/kooo/evcc/midebao/
app/src/main/java/com/galaxy/lyricsnext/GeelyCarDataPanelService.java