跳转至

客户端 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,重连不重放写入。见音量组接入说明。