一、QtIVI 应用开发:应用生命周期管理、窗口管理、输入法框架、持久化存储

各位同学,今天我们来聊聊 QtIVI 开发中几个绕不开的核心话题。说实话,车载系统跟手机 App 差别挺大的。手机 App 挂了可以重启,车载系统要是挂了,用户可能正在导航,那就麻烦了。所以,应用生命周期、窗口管理、输入法、持久化存储,这四块东西,你绕不过去。

我个人习惯把这四个点称为「车载应用的四大支柱」。为什么?因为任何一个车载 HMI 项目,你都得跟它们打交道。今天我就把我在实际项目中踩过的坑、总结的经验,一次性讲清楚。

1.1 应用生命周期管理

先说说生命周期。嗯,这里要注意,QtIVI 的生命周期跟普通 Qt Widgets 或 QML 应用不太一样。普通应用启动、运行、退出,就完了。但车载应用呢?它可能被系统强制休眠,也可能被另一个高优先级应用抢占屏幕。

QtIVI 引入了 ApplicationInterfaceApplicationManager 这两个核心类。它们负责管理应用的状态机。状态包括:

  • Running:应用正在前台运行
  • Suspended:应用被挂起,但进程还在
  • Background:应用在后台运行,可能还在播放音乐或导航语音
  • Stopped:应用被彻底关闭

我在项目中遇到过一个问题:导航应用在后台时,突然被系统杀掉了。用户再切回来,导航路线没了。这就是生命周期管理没做好。正确的做法是:在 Suspended 状态下,保存关键状态;在 Running 状态下,恢复状态。

核心要点:应用状态切换时,一定要处理好数据的保存与恢复。不要等到应用被销毁了才去存数据。

代码示例很简单,但很实用:

// 监听应用状态变化
ApplicationInterface *appInterface = new ApplicationInterface(this);
connect(appInterface, &ApplicationInterface::applicationStateChanged,
        this, [](ApplicationState state) {
    switch (state) {
    case ApplicationState::Suspended:
        // 保存当前导航路线
        saveNavigationRoute();
        break;
    case ApplicationState::Running:
        // 恢复导航路线
        restoreNavigationRoute();
        break;
    default:
        break;
    }
});

你想想看,如果每个应用都自己管自己的状态,那不乱套了?所以 QtIVI 提供了 ApplicationManager 来做统一管理。它负责调度各个应用的生命周期,有点像 Android 的 ActivityManager。

1.2 窗口管理

窗口管理,说白了就是「谁该显示在屏幕上」。车载屏幕通常不止一个,有仪表盘、中控屏、副驾屏、HUD。每个屏幕可能同时显示多个应用。怎么管理?

QtIVI 的窗口管理基于 WindowManagerSurface 的概念。每个应用可以创建多个 Surface,每个 Surface 对应一个窗口。WindowManager 负责决定哪个 Surface 显示在哪个屏幕上。

我记得有一次,客户要求导航应用在仪表盘和中控屏同时显示。但导航应用只有一个进程,怎么搞?后来我们用 WindowManager::addSurface() 给导航应用创建了两个 Surface,一个绑定到仪表盘,一个绑定到中控屏。完美解决。

小技巧:窗口切换时,建议使用 WindowManager::setSurfaceOrder() 来控制 Z 轴顺序。别让弹窗跑到导航地图下面去了。

窗口管理还有一个重要概念:窗口策略。比如,来电时,电话应用必须抢占前台,音乐应用要暂停。这个策略由 WindowManager 的 Policy 模块控制。你可以自定义策略,比如:

// 自定义窗口策略
class MyWindowPolicy : public WindowPolicy {
public:
    void handleSurfaceRequest(Surface *surface, Surface::RequestType type) override {
        if (type == Surface::RequestType::Fullscreen) {
            // 全屏请求,先暂停其他应用
            suspendOtherApplications(surface);
        }
        // 处理请求
        acceptRequest(surface, type);
    }
};

嗯,这里要注意:窗口策略不要写得太死板。不同车型、不同场景,策略可能不一样。比如,有些车在倒车时,倒车影像必须全屏,连导航都得让路。

1.3 输入法框架

输入法,看起来简单,做起来坑多。车载输入法跟手机输入法不一样。手机输入法可以全屏,车载输入法只能占一小块区域。而且,车载输入法要支持旋钮、触摸、语音多种输入方式。

QtIVI 的输入法框架基于 InputMethodInputMethodManager。每个应用可以注册自己的输入法,也可以使用系统默认输入法。

我曾经在项目里遇到一个奇葩问题:用户在搜索目的地时,输入法弹出来了,但键盘挡住了搜索结果列表。后来发现是输入法窗口的 Z 轴顺序没调好。解决方案是:在输入法弹出时,动态调整搜索结果列表的位置。

避坑指南:输入法窗口不要用固定位置。不同屏幕分辨率、不同字体大小,键盘位置可能不一样。建议用 InputMethod::setInputRect() 动态设置输入法区域。

代码示例:

// 注册自定义输入法
InputMethod *myInputMethod = new InputMethod(this);
myInputMethod->setInputRect(QRect(0, 400, 800, 300)); // 动态设置区域
InputMethodManager::instance()->registerInputMethod(myInputMethod);

// 应用端监听输入法事件
connect(myInputMethod, &InputMethod::commitString,
        this, [](const QString &text) {
    // 接收输入文本
    searchDestination(text);
});

说白了,输入法框架的核心就是「输入法进程」和「应用进程」之间的通信。QtIVI 用 D-Bus 或 Qt Remote Objects 来做这个通信。我个人建议用 Qt Remote Objects,性能更好,延迟更低。

1.4 持久化存储

持久化存储,就是「数据怎么存下来,下次启动还能用」。车载应用的数据存储跟普通桌面应用不太一样。桌面应用可以随便读写文件,车载应用要考虑存储空间、读写速度、数据安全。

QtIVI 提供了 StorageManagerPersistentStore 两个核心类。StorageManager 负责管理存储分区,PersistentStore 负责读写键值对数据。

我建议把数据分成三类:

数据类型 存储方式 示例
用户设置 PersistentStore(键值对) 音量、亮度、语言
应用状态 SQLite 数据库 导航历史、播放列表
大文件 文件系统 离线地图、音乐文件

代码示例:

// 使用 PersistentStore 保存用户设置
PersistentStore *store = new PersistentStore(this);
store->setValue("volume", 75);
store->setValue("language", "zh_CN");

// 下次启动时读取
int volume = store->value("volume", 50).toInt();
QString lang = store->value("language", "en_US").toString();

重要提醒:不要在主线程中读写大文件。车载系统的存储通常是 eMMC 或 NAND Flash,读写速度有限。大文件读写一定要放到后台线程,否则 UI 会卡顿。

持久化存储还有一个容易被忽略的点:数据迁移。系统升级后,数据格式可能变了。比如,导航历史从 SQLite 换成了新的格式。怎么办?我建议在应用启动时,检查数据版本号,如果版本号不匹配,执行迁移逻辑。

// 数据迁移示例
int currentVersion = store->value("data_version", 1).toInt();
if (currentVersion < 2) {
    // 执行迁移逻辑
    migrateDataFromV1ToV2();
    store->setValue("data_version", 2);
}

嗯,这个坑我踩过。有一次升级后,用户反馈导航历史全丢了。就是因为没做数据迁移,旧数据被新版本直接覆盖了。

知识体系总览

下面这张图,我把今天讲的四个核心模块串起来了。你可以看到它们之间的关系:

QtIVI 应用开发 应用生命周期管理 Running / Suspended 窗口管理 Surface / Z-Order 输入法框架 InputMethod / 多模态 持久化存储 PersistentStore / SQLite 四大模块协同工作,支撑车载 HMI 应用的稳定运行

从图中你可以看到,这四个模块并不是孤立的。生命周期管理会影响窗口管理(应用挂起时,窗口要隐藏);输入法框架需要持久化存储来保存用户词库;窗口管理又跟输入法框架有交互(输入法窗口的显示与隐藏)。

好了,今天的内容就到这里。这四个模块,每一个单独拿出来都能讲一整天。但作为入门,你先把这些核心概念和代码示例吃透。后面遇到具体项目,你自然知道怎么用。


公众号:蓝海资料掘金营,微信 deep3321