05 - RTI DDS 第二通道
结论先行:RTI 不是 MiCarSettings 的主信号通道,而是针对高频流式数据(充电曲线、充电报告)的补充通道,且仅 8295 平台启用。绝大多数开发者不会碰它。研究车控信号请先看 02 主通道。
1. RTI 是什么
RTI 在本项目里指 RTI Connext DDS(Real-Time Innovations 公司的 Data Distribution Service 中间件),不是小米自研的”信号系统”代号。
证据:
settingsCommon/rtiLib/libs/下是 RTI 官方 Java 库nddsjava.jar/rticonnextmsg.jar- 代码
import com.rti.dds.* - 自动生成的 IDL 类头部注释:
generated from .idl using RTI Code Generator (rtiddsgen) version 3.1.2 - DDS 域
DOMAIN_VEHICLE = 1(整车跨域通信)
DDS 是发布/订阅模型的消息总线,不走 Android Binder,独立于 AOSP CarService 体系。
2. 为什么需要它(与主通道的分工)
| 维度 | 主通道 CarProperty | RTI DDS |
|---|---|---|
| 传输层 | Android Binder → CarService → VHAL | DDS 协议(SOA 中间件,跨域) |
| 数据特征 | 离散控制信号(开关、状态、挡位) | 高频实时流(2Hz 充电曲线、充电报告) |
| 入口 | SettingsCarPropertyManager | RTISignalMgr |
| 信号标识 | propId(int) + areaId | DDS Topic(字符串) + IDL 类型 |
| 业务消费方 | 95+ 文件 | 2 个业务文件 |
| 方向 | 双向(get/set) | 纯订阅(无发布者) |
| 平台 | 全平台 | 仅 8295(DCD),其它回退 AIDL |
简单说:车控开关走 CarProperty,高频数据流走 RTI。
3. 模块结构(settingsCommon/rtiLib/)
包路径:com.android.car.settings.rtilib
| 类 | 职责 |
|---|---|
RTISignalMgr | 核心单例(370 行)。DDS 初始化、DomainParticipant 创建、订阅器注册表、订阅/取消订阅统一入口 |
BaseRTITask<R,D> | 发布/订阅抽象基类。持有 DomainParticipant/Topic,定义 getTopicName()/getTypeName()/registerType() |
BaseRTISub<R,D> | 订阅基类。创建 Subscriber + DataReader,监听 on_data_available,回调切主线程 |
BaseUnboundRTISub<R,D> | 无界数组订阅器(Kotlin),用 MicarBestEffort QoS,initial_samples=1 |
IReceiveSubDataListener<D> | 业务侧回调接口:onReceiveSubDataRunOnMainThread(D d) |
OnInitRTISuccessPendingTask | RTI init 成功前暂存任务(解决时序问题) |
RtiConstant | QoS XML 路径:/vendor/etc/micar_mw_configs/rtidds_qos/MICAR_QOS_PROFILES.xml |
RtiSslDdsQosHelper | DDS Security + QoS 加载 |
DeviceIdUtils | 判断平板(elish/enuma/nabu),平板禁用 RTI |
注意:没有 RtiClient/RtiManager/RtiService,rtilib 内也没有任何 .aidl 文件。
4. 订阅链路(带源码行号)
4.1 业务侧入口(充电报告)
文件:settingsPage/micarChargeSettings/src/main/java/com/android/micar/settings/energy/repo/ChargingReportRepo.kt
// L59-60 订阅器工厂
private val mChargeReportSubscriber =
Function<DomainParticipant, BaseRTISub<*, *>> { ChrgnReportDataSubscriber(it) }
// L82-89 平台门控 + 三步注册
private fun initDataSource() {
if (MiCarSettings.is8295Architecture()) { // 仅 8295
RTISignalMgr.getInstance().registerSubscriber(mChargeReportSubscriber) // ① 注册工厂
RTISignalMgr.getInstance().init(BaseApplication.getInstance().applicationContext) // ② 触发 DDS init
RTISignalMgr.getInstance().addReceiveSubDataListener( // ③ 加业务回调
RTISignalConstants.TOPIC_CHARGE_REPORT_DATA, mRtiSubDataListener
)
} else {
ChargingReportMgr.registerListener(mDrivingListener) // 非 8295 回退 AIDL
}
}4.2 管理器侧(RTISignalMgr.java)
// L231-251 addReceiveSubDataListener 内部转成 pending task
public void addReceiveSubDataListener(String topicName, IReceiveSubDataListener listener) {
OnInitRTISuccessPendingTask task = new OnInitRTISuccessPendingTask() {
public void onInitRTISuccess() {
BaseRTISub sub = mSubMap.get(topicName);
if (sub.getListenerSize() == 0) {
startSubscriber(topicName); // 自动启动 DataReader
}
sub.addReceiveSubDataListener(listener);
}
// ...
};
addInitSuccessPendingTask(task); // 未 init 成功则排队
}4.3 DDS 数据到达 → 业务回调(BaseRTISub.java)
// L164 DDS 推送
public void on_data_available(DataReader dataReader) {
if (mListener != null) mListener.apply((M) dataReader);
}
// L93-98 切主线程 + 分发
protected void receiveData(D data) {
mHandler.post(() -> {
for (IReceiveSubDataListener<D> l : mReceiveSubDataListenerSet) {
l.onReceiveSubDataRunOnMainThread(data); // ★ 业务回调触发
}
});
}4.4 具体订阅器实现(模板方法)
文件:settingsPage/micarChargeSettings/src/main/java/com/android/micar/settings/energy/dds/ChrgnReportDataSubscriber.kt
class ChrgnReportDataSubscriber(domainParticipant: DomainParticipant) :
BaseUnboundRTISub<ChrgnReportDataDataReader, ChrgnReportData>(domainParticipant) {
override fun readData(dataReader: ChrgnReportDataDataReader) {
// dataReader.take(...) 取出 DDS 数据
for (i in 0 until dataSeq.size) {
if (info.valid_data) {
receiveData(ChrgnReportData(dataSeq[i])) // ★ 回调到 BaseRTISub.receiveData
}
}
}
override fun getTopicName() = RTISignalConstants.TOPIC_CHARGE_REPORT_DATA
override fun getTypeName() = ChrgnReportDataTypeSupport.get_type_name()
override fun registerType() { ChrgnReportDataTypeSupport.register_type(mDomainParticipant, typeName) }
}5. 信号标识(Topic + IDL)
RTI 里没有数字 ID,信号由 DDS Topic 名 + IDL 类型唯一标识。所有 Topic 集中定义:
文件:settingsPage/micarChargeSettings/src/main/java/com/android/micar/settings/energy/dds/RTISignalConstants.java
public static final String TOPIC_HV_CHARGING_POWER_IN_ONE_CYCLE = "HvChargingPowerInOneCycleTopic";
public static final String TOPIC_CHARGE_REPORT_DATA = "ChrgnReportDataTopic";
public static final int TOPIC_HV_CHARGING_POWER_IN_ONE_CYCLE_SIZE = 101; // FloatSeq 长度Topic/IDL 来自仓库 micar_soa(https://git.n.xiaomi.com/group_soa/micar_soa_public/micar_soa),由 rtiddsgen 生成 Java 强类型类。
6. 关于”发布”
IGetPublishDataListener<D> 接口存在(getPublishData()),BaseRTITask 也留了发布侧位置,但全项目没有任何类实现 IGetPublishDataListener,也没有 DataWriter 的业务调用。
也就是说:RTI 在本项目里纯粹是订阅端/数据消费者。Settings 应用只接收车辆 SOA 推过来的高频数据,不向外发布。
7. 实际消费方清单
RTI 的业务消费方全项目仅 2 处,都在 micarChargeSettings:
| 文件 | 用途 |
|---|---|
settingsPage/micarChargeSettings/src/main/java/com/android/micar/settings/energy/repo/ChargingReportRepo.kt | 充电报告 |
settingsPage/micarChargeSettings/src/main/java/com/android/micar/settings/energy/dds/ChrgnReportDataSubscriber.kt | 充电报告订阅器 |
settingsPage/micarChargeSettings/src/main/java/com/android/micar/settings/energy/dds/HvChargingPowerInOneCycleSubscriber.kt | 充电功率曲线订阅器 |
settingsPage/micarChargeSettings/src/dcddif/java/com/android/micar/settings/energy/view/EnergyChargeCurveActivity.kt | 充电曲线页(仅 dcd flavor) |
8. 常见误读澄清
“grep
rti命中 335 个文件,RTI 用得很广?”
误读。绝大多数命中是英文单词子串:friction / partition / description / configuration / attribute / operator 都包含 “rti”。
精确搜索:
RTISignalMgr类名:业务侧引用仅 2 个文件 / 14 处调用import com.rti.dds.*:真实使用 17 个文件(多为 rtiLib 自身 + 充电模块生成的 IDL 类)
9. 关键路径速查
| 主题 | 路径 |
|---|---|
| RTI 单例 | settingsCommon/rtiLib/src/main/java/com/android/car/settings/rtilib/RTISignalMgr.java |
| 订阅基类 | settingsCommon/rtiLib/src/main/java/com/android/car/settings/rtilib/BaseRTISub.java |
| 无界订阅器 | settingsCommon/rtiLib/src/main/java/com/android/car/settings/rtilib/BaseUnboundRTISub.kt |
| Topic 常量 | settingsPage/micarChargeSettings/src/main/java/com/android/micar/settings/energy/dds/RTISignalConstants.java |
| 业务消费示例 | settingsPage/micarChargeSettings/src/main/java/com/android/micar/settings/energy/repo/ChargingReportRepo.kt |
10. 什么时候该用 RTI(给开发者的决策建议)
几乎从不需要自己加 RTI 订阅。判断标准:
- ✅ 数据是高频连续流(≥1Hz,如实时功率曲线、振动数据)
- ✅ 主通道 CarProperty 无法承载(频率太高会丢信号,CONTINUOUS prop 有上限)
- ✅ 已确认 SOA 侧通过 DDS Topic 下发该数据
- ✅ 仅 8295 平台需要,或愿意维护双路径(RTI + AIDL 回退)
不满足以上任一条件,用主通道 CarProperty,参见 09 开发模板。