跳转至

IEvccApiCallback 回调接口

最后更新:

AIDL:com.kooo.evcc.evccapi.IEvccApiCallback

契约状态 值
引入版本 EvccApi 1.0
最近接口变更 EvccApi 1.1
稳定性 稳定公开接口
弃用状态 未弃用

IEvccApiCallback 是客户端在 openSession() 时交给 EVCC 的单向事件通道。属性和变量监听、来源状态、 Shell 最终结果、会话关闭及重同步通知都从这里返回。

oneway interface IEvccApiCallback {
    void onSessionClosed(in EvccOperationResult reason);
    void onPropertyEvent(
        in EvccPropertyRecord record,
        long sequence,
        String eventType
    );
    void onVariableEvent(
        in EvccVariableRecord record,
        long sequence,
        String eventType
    );
    void onSourceStateChanged(in EvccSourceState state, long sequence);
    void onShellCompleted(in EvccShellResult result);
    void onResyncRequired(long expectedSequence, long actualSequence);
}

回调通用规则

oneway 不等于主线程

所有方法都是 oneway Binder 回调:EVCC 提交回调后不会等待客户端业务处理完成。Raw AIDL 客户端收到 回调时通常位于 Binder 线程,必须快速复制/入队;数据库、文件、网络和复杂解析要转交自己的执行器。 EvccApiClient 会把 Listener 回调分发到主线程,但应用仍应把耗时工作移出主线程。

回调可以与同步会话方法并发。例如 replaceSubscriptions() 产生的 INITIAL_SNAPSHOT 可能在调用方处理 同步返回前进入客户端队列。调用前先准备好接收状态,不要依赖“方法返回后才会回调”。

共享事件序号

属性事件、变量事件和来源状态共用一个会话级 sequence:

  • 新会话从 1 开始递增;
  • 三类事件必须按同一条序列检查,不能各自维护计数器;
  • Shell 完成和会话关闭不参与该序列;
  • 发现 sequence != lastSequence + 1 时,已有增量基线可能不完整,应重新提交属性和变量订阅;
  • 新建会话后序号重新开始,不能跨会话比较。

服务端普通事件队列当前最多暂存 128 条。队列溢出时会清除已失去连续性的积压事件,并发送一次 onResyncRequired();该容量是保护边界,不是允许客户端长期阻塞回调的承诺。

建议的 Raw AIDL 处理骨架

private val callback = object : IEvccApiCallback.Stub() {
    override fun onPropertyEvent(
        record: EvccPropertyRecord,
        sequence: Long,
        eventType: String,
    ) {
        callbackExecutor.execute { consumeProperty(record, sequence, eventType) }
    }

    override fun onVariableEvent(
        record: EvccVariableRecord,
        sequence: Long,
        eventType: String,
    ) {
        callbackExecutor.execute { consumeVariable(record, sequence, eventType) }
    }

    // 其余方法同样只做快速转交。
}

客户端必须在会话存活期间保持 callback 强引用。callback Binder 死亡时 EVCC 会关闭会话并清理需求。

onSessionClosed(reason)

用途:通知客户端当前会话已经终止,不能再提交业务调用。

void onSessionClosed(in EvccOperationResult reason);

参数格式:

参数 类型 含义
reason EvccOperationResult status 是关闭类别,message 是展示/诊断说明

常见状态:

  • PERMISSION_DENIED:用户撤权、授权修订号/UID/签名变化,或回调前实时校验失败;
  • SERVICE_DISABLED:EVCC 开放 API 被关闭;
  • SESSION_CLOSED:服务停止、全部解绑、客户端进程退出或回调处理持续过慢。

客户端动作:停止使用旧 IEvccApiSession,取消依赖该会话的等待任务并更新 UI。只有临时服务断连 适合退避重绑;明确撤权或服务关闭时等待用户处理后再主动连接。

override fun onSessionClosed(reason: EvccOperationResult) {
    currentSession = null
    notifyUi(reason.status, reason.message)
}

注意事项:这是终态回调,没有 sequence。EVCC 在它之前会清空未发送的普通和 Shell 回调;收到后 不要等待该会话的其他结果。客户端主动调用 close() 时不会再收到本回调。

onPropertyEvent(record, sequence, eventType)

用途:发送已订阅属性的初始值和后续变化。

void onPropertyEvent(
    in EvccPropertyRecord record,
    long sequence,
    String eventType
);

参数格式:

参数 类型 含义
record EvccPropertyRecord 已解析来源、描述符、类型化值和当前状态
sequence long 会话级共享事件序号
eventType String 本次事件的语义类型

事件类型:

eventType 含义 客户端处理
INITIAL_SNAPSHOT 新订阅或来源换代后的基线 替换该物理目标的旧值,即使状态是 PENDING
VALUE_CHANGED 同一来源、ID、区域的值变化 在连续序号基线上应用增量
SOURCE_CHANGED AUTO 目标切换到另一实际来源 同时更新来源身份和当前值,不只替换 value

REMOVED 是共享事件常量,但当前 1.0 仅用于变量删除;属性订阅路径不会发送该类型。

状态处理:事件到达不等于值可用。始终先检查 record.status,再检查 record.value?.initialized == true;初始属性尚无真实值时可返回 PENDING。

override fun onPropertyEvent(
    record: EvccPropertyRecord,
    sequence: Long,
    eventType: String,
) {
    checkSequenceOrResync(sequence)
    val key = Triple(
        record.resolvedSource,
        record.descriptor?.canonicalId,
        record.descriptor?.areaId,
    )
    propertyState[key] = record
}

注意事项:AUTO 订阅在正常瞬时断连时保持原来源,不立即切换;来源关闭或持续不支持后才可能产生 SOURCE_CHANGED。一次回调可能因 Binder 大小预算省略超大值并返回 PAYLOAD_TOO_LARGE。

onVariableEvent(record, sequence, eventType)

用途:发送已订阅变量的初始状态和后续变化。

void onVariableEvent(
    in EvccVariableRecord record,
    long sequence,
    String eventType
);

参数格式:

参数 类型 含义
record EvccVariableRecord 变量名、类型化值、状态、更新时间及可变性
sequence long 与属性和来源事件共享的会话级序号
eventType String INITIAL_SNAPSHOT、VALUE_CHANGED 或 REMOVED

行为:订阅合法但当前不存在的变量也会产生 INITIAL_SNAPSHOT,此时记录状态为 NOT_FOUND。 REMOVED 表示变量被删除;不能把 value == null 单独当作传输错误。

override fun onVariableEvent(
    record: EvccVariableRecord,
    sequence: Long,
    eventType: String,
) {
    checkSequenceOrResync(sequence)
    if (eventType == EvccApiContract.EventType.REMOVED) {
        variables.remove(record.name)
    } else {
        variables[record.name] = record
    }
}

注意事项:重新调用 replaceVariableSubscriptions() 后,旧订阅代次中已排队但尚未发送的事件会被 服务丢弃,因此客户端可能观察到序号缺口。将缺口视为重同步信号,不要自行猜测缺失变量值。

onSourceStateChanged(state, sequence)

用途:通知与当前属性订阅相关的数据源状态或连接代次变化。

void onSourceStateChanged(in EvccSourceState state, long sequence);

参数格式:

参数 类型 含义
state EvccSourceState 来源 ID、AVAILABLE/PENDING/UNAVAILABLE、说明和来源代次
sequence long 与属性和变量事件共享的会话级序号
override fun onSourceStateChanged(state: EvccSourceState, sequence: Long) {
    checkSequenceOrResync(sequence)
    sourceStates[state.source] = state
}

注意事项:sourceGeneration 只在同一来源内比较变化,不能跨来源比较大小。状态回调用于解释订阅 为何等待或切换,不要求第三方应用自行连接、断开或维护五个上游来源。

onShellCompleted(result)

用途:返回 executeShell() 的最终执行结果。

void onShellCompleted(in EvccShellResult result);

参数格式:EvccShellResult:

字段 含义
requestId 与提交/取消使用的 ID 一致
status 最终 OK、FAILED、CANCELLED 或 PERMISSION_DENIED 等
exitCode 进程退出码;未启动或无退出码时为 -1
stdout / stderr 受权限和大小预算限制的输出
truncated 输出是否被裁剪
durationMs 从开始执行到结束的耗时
override fun onShellCompleted(result: EvccShellResult) {
    pendingShell.remove(result.requestId)
    if (result.status == EvccApiContract.Status.OK && result.exitCode == 0) {
        showOutput(result.stdout, result.truncated)
    } else {
        showFailure(result.status, result.stderr)
    }
}

注意事项:本回调没有事件序号,必须用 requestId 关联。Shell 结果使用独立的高优先级队列,当前 最多暂存 16 条;队列满时 EVCC 会关闭处理过慢的会话,而不会静默丢失不可重放的结果。即使同步提交 返回 ACCEPTED 或 PENDING,也只能以本回调作为最终执行结果。

onResyncRequired(expectedSequence, actualSequence)

用途:明确通知普通事件队列已经出现不可恢复的缺口,客户端必须重建属性和变量基线。

void onResyncRequired(long expectedSequence, long actualSequence);

参数格式:

参数 含义
expectedSequence 客户端原本应收到的下一事件序号
actualSequence 服务端缺口阶段已经推进到的序号;重同步跟踪可从这里重置

Raw AIDL 客户端处理顺序:

  1. 标记当前增量派生状态不再完整,暂停基于旧基线的计算。
  2. 将本地序号基准重置为 actualSequence。
  3. 重新调用 replaceSubscriptions(完整属性集合)。
  4. 重新调用 replaceVariableSubscriptions(完整变量集合)。
  5. 若启用了音乐监听,再调用 setMusicSubscription(true)。
  6. 等待新一轮 INITIAL_SNAPSHOT,再恢复业务计算。
override fun onResyncRequired(expectedSequence: Long, actualSequence: Long) {
    callbackExecutor.execute {
        sequenceTracker.reset(actualSequence)
        session.replaceSubscriptions(desiredProperties)
        session.replaceVariableSubscriptions(desiredVariables)
        if (musicEnabled) session.setMusicSubscription(true)
    }
}

注意事项:即使没有收到本回调,客户端自行发现共享 sequence 跳号也应执行相同流程。 EvccApiClient 已实现缺口检测、整批重放和序号重置;应用仍需在收到新初始快照后重建自己的派生状态。

会话方法和同步返回见IEvccApiSession。

onMusicStateChanged(state, sequence, eventType)(1.1)

void onMusicStateChanged(in EvccMusicState state, long sequence, String eventType);

仅显式开启音乐订阅后触发。首次/重复开启为 INITIAL_SNAPSHOT,后续为 VALUE_CHANGED;sequence 与属性、变量、来源事件共享。音乐状态含当前行和资源版本,不含完整歌词或图片字节。完整歌词版本变化后按需读取;SDK 默认自动加载。音乐 SERVICE_DISABLED 只表示音乐来源未运行,不等同于会话关闭。