SDL应用生命周期:从启动到退出的完整旅程
每个SDL应用,说白了就是一辆车的「乘客」。它什么时候上车、什么时候下车、在车上做什么,都得遵循一套规矩。这套规矩,就是SDL应用生命周期。
我个人习惯把SDL应用的生命周期分成四个阶段:注册、激活、运行、注销。你想想看,这跟咱们手机上的App其实很像——安装、打开、使用、关闭。
注册阶段:应用与HMI的第一次握手
应用启动后,第一件事就是向SDL Core发送RegisterAppInterface请求。这个请求里包含了应用的基本信息:
{
"appName": "我的车载音乐",
"appID": "com.example.mymusic",
"languageDesired": "ZH-CN",
"hmiDisplayLanguageDesired": "ZH-CN",
"appType": ["MEDIA"]
}
嗯,这里要注意:appID必须唯一,否则SDL Core会直接拒绝注册。我在项目中遇到过两次因为appID冲突导致应用无法启动的坑,后来我们团队规定所有appID必须走统一注册流程。
注册成功后,SDL Core会返回RegisterAppInterfaceResponse,里面包含了HMI支持的功能列表。这时候应用才算真正「上车」了。
激活阶段:从后台到前台
注册成功不代表应用就能立刻显示在屏幕上。SDL应用有四种HMI状态:
| HMI状态 | 说明 | 可执行操作 |
|---|---|---|
| FULL | 全屏显示,用户正在使用 | 所有UI操作 |
| LIMITED | 部分显示,可能被其他应用遮挡 | 音频播放、有限UI |
| BACKGROUND | 后台运行,不可见 | 仅数据同步 |
| NONE | 未激活,几乎不能做任何事 | 仅能发送注册请求 |
应用刚注册完,默认是BACKGROUND状态。要进入FULL状态,需要用户主动点击或语音唤醒。我曾经做过一个导航应用,用户一上车就自动请求FULL状态,结果被HMI拒绝了——因为当时音乐应用正在前台。后来我加了个策略:先监听OnHMIStatus通知,等HMI状态变化了再决定下一步。
运行阶段:RPC请求与响应的交响乐
应用进入FULL状态后,就可以跟HMI进行交互了。SDL的交互方式就是RPC(Remote Procedure Call,远程过程调用)。说白了,就是应用发一个请求,HMI处理完再返回一个响应。
RPC请求的格式很固定:
// 请求示例:显示一个按钮
{
"id": 1,
"jsonrpc": "2.0",
"method": "SDL.Show",
"params": {
"mainField1": "欢迎使用车载音乐",
"softButtons": [
{
"type": "TEXT",
"text": "播放",
"systemAction": "DEFAULT_ACTION",
"softButtonID": 100
}
]
}
}
响应格式也类似:
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"code": 0,
"method": "SDL.Show",
"message": "成功"
}
}
你可能会问:为什么用JSON-RPC而不是REST?我个人理解,SDL的设计初衷是让应用和HMI之间保持一种「轻量级、低延迟」的通信。JSON-RPC的请求-响应模式天然适合这种场景,而且每个请求都有唯一ID,方便追踪。
SDL UI组件:Button、List、Alert
SDL的UI组件不多,但每个都很实用。我个人觉得,掌握这三个组件就能覆盖90%的车载交互场景。
Button:最基础的交互单元
SDL中的Button分为两种:SoftButton(软按钮)和CustomButton(自定义按钮)。
SoftButton是预定义的,比如「播放」「暂停」「上一首」「下一首」。它们有固定的图标和位置,应用只需要指定按钮ID和文本即可。
CustomButton则灵活得多,可以自定义图标、文本、甚至颜色。但要注意,不是所有HMI都支持CustomButton。我在项目中就遇到过某款车机不支持自定义按钮,只能用SoftButton。
// 创建SoftButton
SoftButton playButton = new SoftButton()
.setType(SoftButtonType.SBT_TEXT)
.setText("播放")
.setSoftButtonID(100)
.setSystemAction(SystemAction.DEFAULT_ACTION);
// 发送Show请求显示按钮
Show showRequest = new Show()
.setMainField1("当前歌曲:晴天")
.setSoftButtons(Arrays.asList(playButton));
List:信息展示的利器
SDL的List组件叫ChoiceSet,它跟手机上的列表不太一样。车载场景下,用户不能像手机那样滑动点击,所以SDL的列表设计成了「语音+按键」的交互方式。
每个列表项都有一个choiceID,用户可以通过语音说出选项内容,或者用旋钮/方向盘按键选择。我刚开始做SDL开发时,总想用列表展示大量数据,结果发现用户根本操作不过来。后来我学乖了:列表项不超过6个,每个选项的文字不超过10个字。
// 创建选择列表
Choice choice1 = new Choice()
.setChoiceID(1)
.setMenuName("播放列表");
Choice choice2 = new Choice()
.setChoiceID(2)
.setMenuName("搜索歌曲");
// 发送PerformInteraction请求
PerformInteraction pi = new PerformInteraction()
.setInitialText("请选择操作")
.setInteractionChoiceSet(Arrays.asList(choice1, choice2))
.setInteractionMode(InteractionMode.BOTH); // 支持语音和按键
Alert:关键时刻的提醒
Alert是SDL中唯一可以「打断」用户当前操作的组件。比如导航提示、来电提醒、紧急通知等。
Alert有几个关键参数:
- alertText1/alertText2/alertText3:三行文本,从上到下排列
- duration:显示时长,单位毫秒
- softButtons:可以附带按钮,比如「忽略」「查看」
- playTone:是否播放提示音
SDL HMI交互:应用与车机的对话
SDL的HMI交互,说白了就是应用和车机屏幕之间的「对话」。这个对话通过SDL Core中转,应用不需要直接操作硬件。
交互流程:三步走
- 应用发送RPC请求:比如显示一个按钮、弹出一个Alert
- SDL Core转发给HMI:Core会检查请求是否合法,比如按钮ID是否重复、文本是否超长
- HMI处理并返回响应:HMI把结果显示在屏幕上,然后告诉Core「处理完了」
这个流程看起来简单,但有个关键点:应用不能直接操作HMI。所有UI操作都必须通过SDL Core。为什么这么设计?我个人的理解是,车载系统对安全性要求极高,不能让每个应用都能直接控制屏幕,万一有个应用崩溃了,整个车机都跟着遭殃。
事件监听:被动接收HMI通知
除了主动发送请求,应用还需要监听HMI发来的事件。比如用户点击了按钮、HMI状态变化了、语音识别结果出来了。
这些事件通过OnXxx系列通知传递:
// 监听按钮点击事件
@Override
public void onOnButtonPress(OnButtonPress notification) {
int buttonID = notification.getButtonID();
if (buttonID == 100) {
// 用户点击了「播放」按钮
startPlayback();
}
}
// 监听HMI状态变化
@Override
public void onOnHMIStatus(OnHMIStatus notification) {
HMILevel level = notification.getHMILevel();
if (level == HMILevel.HMI_FULL) {
// 应用进入前台,可以开始交互
showMainScreen();
}
}
嗯,这里有个小技巧:不要在事件回调里做耗时操作。SDL的事件回调是在主线程执行的,如果你在里面做网络请求或者数据库操作,会阻塞UI更新。我习惯的做法是:在回调里只做状态标记,真正的业务逻辑放到子线程或者Handler里处理。
交互模式:语音与触控的平衡
车载交互跟手机最大的不同,就是语音优先。用户开车时不能盯着屏幕点来点去,所以SDL的UI组件都支持语音操作。
比如前面提到的ChoiceSet,用户可以直接说「播放列表」来选中对应项。但语音识别不是100%准确的,所以SDL也保留了触控和旋钮操作。我个人建议:所有UI组件都要同时支持语音和触控,不要只依赖一种交互方式。
知识体系总览
下面这张图展示了SDL应用开发的核心知识结构,从生命周期到RPC通信,再到UI组件和HMI交互,环环相扣。
这张图把SDL应用开发的核心知识点串起来了。你想想看,从应用启动到用户交互,每一步都离不开这四个模块的配合。我个人觉得,掌握了这些,SDL基础应用开发就算入门了。
公众号:蓝海资料掘金营,微信deep3321