客户端 SDK 与生命周期¶
最后更新:
evccapi-client 是第三方 Android 应用的推荐接入层。它依赖公开的 evccapi-contract,负责绑定
EVCC、校验协议主版本、建立授权会话、监听 Binder 死亡、退避重连、检查事件序号,并在重连或事件
缺口后整批恢复最后一次成功提交的属性和变量订阅。
SDK 不会替应用申请授权,也不会把同步 Binder 调用自动变成异步调用。属性读取、目录、写入、动作和 Shell 等方法仍应由应用放到工作线程执行。
引入模块与 Manifest¶
当前公开包提供两个 Android library module,最低 Android 版本为 API 28,使用 Java 11 字节码:
// settings.gradle.kts
include(":evccapi-contract", ":evccapi-client")
// app/build.gradle.kts
dependencies {
implementation(project(":evccapi-client"))
}
evccapi-client 通过 api(project(":evccapi-contract")) 暴露 AIDL 接口和 Parcelable 模型。应用
Manifest 还需要声明普通权限和 EVCC 包可见性:
<uses-permission android:name="com.kooo.evcc.permission.ACCESS_EVCC_API" />
<queries>
<package android:name="com.kooo.evcc" />
</queries>
Manifest 声明不等于 EVCC 内授权。用户仍须在 EVCC 开放 API 的“开放权限的应用”中勾选该应用; 授权边界详见权限与授权。
连接状态¶
| 状态 | 含义 |
|---|---|
DISCONNECTED |
未连接、主动断开,或发生必须由用户处理的拒绝 |
BINDING |
已发起绑定,正在等待 Binder 和会话握手 |
CONNECTED |
IEvccApiSession 已建立,可以提交业务调用 |
WAITING_TO_RETRY |
临时断连,SDK 正按退避计划等待重绑 |
绑定握手超时为 15 秒。临时错误的重试间隔依次为 1、2、5、10、30 秒,之后保持 30 秒。明确的
PERMISSION_DENIED、SERVICE_DISABLED、空 Binder、绑定 SecurityException 或协议主版本不兼容
会停止自动重试;用户调整授权或配置后,应用应再次调用 connect()。
disconnect() 可重复使用:它断开当前会话但保留最后一次成功的订阅需求,下一次 connect() 成功后
会恢复这些需求。close() 是终态,会释放客户端内部协程;调用后不能再次连接。
Kotlin 接入¶
下面示例把客户端生命周期绑定到一个 Activity。onSessionReady 和其他 SDK 监听回调在主线程分发,
同步读取则显式切换到 Dispatchers.IO:
class ApiActivity : AppCompatActivity() {
private val client by lazy {
EvccApiClient(applicationContext, listener = object : EvccApiClient.Listener {
override fun onSessionReady(apiInfo: EvccApiInfo, sessionInfo: EvccSessionInfo) {
lifecycleScope.launch {
val record = withContext(Dispatchers.IO) {
client.readProperty(
EvccPropertySelector(id = 557849089L),
forceRefresh = false,
)
}
// 先检查 record.status,再读取 record.value。
}
}
override fun onResyncRequired(expectedSequence: Long, actualSequence: Long) {
// 暂停依赖旧增量基线的计算,等待新的 INITIAL_SNAPSHOT。
}
override fun onError(operation: String, error: Throwable) {
// 显示可操作的错误;不要在这里无间隔重试。
}
})
}
override fun onStart() {
super.onStart()
client.connect()
}
override fun onStop() {
client.disconnect()
super.onStop()
}
override fun onDestroy() {
client.close()
super.onDestroy()
}
}
如果同一客户端由进程级组件持有,应在最后一个业务消费者离开时先用空列表清除属性与变量订阅,再
disconnect();不要为了避免重绑而让无业务需求的车辆属性订阅常驻。
Listener 与 Flow¶
addListener() / removeListener() 适合 Java 或统一事件分发。Kotlin 也可以收集两个 Flow:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
client.propertyEvents().collect { event ->
// event.record / event.sequence / event.eventType
}
}
launch {
client.variableEvents().collect { event ->
// event.record / event.sequence / event.eventType
}
}
}
}
Flow 只封装属性和变量事件。来源状态、Shell 完成结果、会话关闭和重同步通知仍通过 Listener 获取。
所有监听回调都应快速完成;耗时解析、数据库写入和网络操作需转交工作线程。
Java 接入¶
公开模型提供 Java getter 和 @JvmOverloads 构造函数,EvccApiClient.Listener 的方法也都有默认
实现。Java 应用可以只覆盖需要的回调:
public final class ApiActivity extends AppCompatActivity {
private final ExecutorService binderExecutor = Executors.newSingleThreadExecutor();
private EvccApiClient client;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
client = new EvccApiClient(getApplicationContext());
client.addListener(new EvccApiClient.Listener() {
@Override
public void onSessionReady(EvccApiInfo apiInfo, EvccSessionInfo sessionInfo) {
binderExecutor.execute(() -> {
try {
EvccPropertySelector selector = new EvccPropertySelector(557849089L);
EvccPropertyRecord record = client.readProperty(selector, false);
runOnUiThread(() -> {
// 先检查 record.getStatus(),再读取 record.getValue()。
});
} catch (RemoteException | IllegalStateException error) {
runOnUiThread(() -> {
// 连接可能已失效;等待 SDK 状态回调。
});
}
});
}
@Override
public void onResyncRequired(long expectedSequence, long actualSequence) {
// 等待 SDK 重新提交订阅后产生的 INITIAL_SNAPSHOT。
}
});
}
@Override protected void onStart() {
super.onStart();
client.connect();
}
@Override protected void onStop() {
client.disconnect();
super.onStop();
}
@Override protected void onDestroy() {
client.close();
binderExecutor.shutdownNow();
super.onDestroy();
}
}
Java 调用 EvccValue.ofString/ofLong/ofDouble/ofBoolean 时需要显式传入第二个 observedAt 参数;例如
EvccValue.ofBoolean(true, System.currentTimeMillis())。该参数对客户端写入只是模型字段,服务仍会按
目标属性类型验证实际值。全部字段和 Java 可空性见数据模型参考。
订阅恢复和事件缺口¶
replaceSubscriptions() 与 replaceVariableSubscriptions() 都是“整集合替换”,不是追加。SDK 只在
服务返回 OK 或 ACCEPTED 时保存新集合,并在以下场景重新提交:
- Binder 重连并建立新会话;
- 服务调用
onResyncRequired(); - SDK 发现属性、变量和来源状态共享的事件序号出现缺口。
重新提交会触发新的 INITIAL_SNAPSHOT。SDK 负责恢复传输层需求,但不会重建应用自己的派生状态;
应用应按初始化、监听与重同步重新建立业务基线。五种属性源的选择和 APVP 按需生命周期
见属性模型与读写,不要在客户端自行维护上游连接。
同步调用与异常边界¶
queryCapabilities()、属性/变量读写、目录、动作和 Shell 提交都是同步 Binder 调用。- 未连接时 SDK 包装方法抛出
IllegalStateException。 - Binder 传输失败通常抛出
RemoteException;业务失败优先通过记录或EvccOperationResult.status返回,处理方式见状态与错误码。 currentApiInfo()、currentSessionInfo()和currentSession()只是当前连接快照,未连接时为null; 不要长期缓存底层IEvccApiSession绕过 SDK 重连。openSettings()可以在未连接时调用,只负责打开 EVCC 设置页,不能自动授权。- 当前源码 SDK 的
openAuthorization()可以在未连接时调用,按当前应用包名打开其授权窗口, 不预选权限或自动授权。没有该方法的已发布 SDK 可直接使用Intent Action。 返回false时可回退到openSettings();成功启动后,返回应用仍须重新连接并检查会话权限。
需要排查 SDK 与 Binder 哪一层出错时,可使用 Demo 的 Raw AIDL 模式;直接接入的精确接口签名见 AIDL 接口参考。
API 1.1 音乐能力¶
新增 readMusicState、setMusicSubscription、openMusicLyrics/openMusicArtwork、sendMusicCommand、musicEvents 和音乐 Listener;重连/缺口后恢复音乐订阅。自动完整歌词默认开启,可用 setAutomaticMusicLyrics(false) 关闭。EvccMusicResources.positionAt 提供进度外推。
API 1.2 音量组¶
SDK 和 Demo 1.2.0 提供查询、读取、设置和增减,包含 Java/Kotlin 示例。先确认协议与独立音量权限;旧服务返回 UNSUPPORTED,重连不重放写入。见音量组接入说明。