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. 为什么需要它(与主通道的分工)

维度主通道 CarPropertyRTI DDS
传输层Android Binder → CarService → VHALDDS 协议(SOA 中间件,跨域)
数据特征离散控制信号(开关、状态、挡位)高频实时流(2Hz 充电曲线、充电报告)
入口SettingsCarPropertyManagerRTISignalMgr
信号标识propId(int) + areaIdDDS 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)
OnInitRTISuccessPendingTaskRTI init 成功前暂存任务(解决时序问题)
RtiConstantQoS XML 路径:/vendor/etc/micar_mw_configs/rtidds_qos/MICAR_QOS_PROFILES.xml
RtiSslDdsQosHelperDDS 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_soahttps://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 开发模板