Embedded

Загрузочный экран (splash screen) и быстрый старт embedded-приложения

Введение

На embedded-устройствах время от включения питания до полностью работающего интерфейса часто критично — пользователь ожидает, что устройство (терминал, киоск, бортовой дисплей) станет отзывчивым за секунды, а не за десятки секунд. В статье разберём, как организовать загрузочный экран (splash screen), скрывающий время инициализации, и общие приёмы ускорения старта Qt-приложения на embedded.

Концепция

Полное время «от включения питания до рабочего интерфейса» складывается из нескольких этапов: загрузка загрузчика и ядра Linux, инициализация системных сервисов, запуск самого Qt-приложения, и инициализация внутренней логики приложения (подключение к базе данных, загрузка конфигурации, первичная отрисовка QML-сцены). Splash screen решает только последнюю часть этой цепочки — показывает простое статичное изображение или анимацию максимально быстро после запуска самого процесса приложения, пока в фоне продолжается более долгая инициализация — но для действительно быстрого старта системы в целом нужна оптимизация и более ранних этапов (минимизация набора загружаемых системных сервисов, отложенная инициализация некритичных частей самого Qt-приложения).

Пример кода: Загрузочный экран (splash

// main.cpp — показ простого splash screen максимально быстро, до полной инициализации QML
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QTimer>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    // Самый быстрый возможный способ показать что-то на экране —
    // минимальное QML-окно со статичным изображением, загружаемое первым
    QQmlApplicationEngine splashEngine;
    splashEngine.loadFromModule("EmbeddedDashboard", "SplashScreen");

    // Основная, более тяжёлая инициализация запускается ПОСЛЕ показа splash screen,
    // не блокируя пользователя видом пустого чёрного экрана
    QTimer::singleShot(0, [&app]() {
        auto *mainEngine = new QQmlApplicationEngine(&app);
        mainEngine->loadFromModule("EmbeddedDashboard", "Main");
        // splash screen скрывается автоматически при готовности основного окна
    });

    return app.exec();
}
// SplashScreen.qml — максимально простой, быстро рисуемый экран загрузки
import QtQuick

Window {
    visible: true
    width: 800
    height: 480
    flags: Qt.SplashScreen // специальный флаг окна для экрана загрузки без декораций

    Rectangle {
        anchors.fill: parent
        color: "#1a1a2e"

        Image {
            anchors.centerIn: parent
            source: "logo.png" // простое статичное изображение, не сложная анимация
        }
    }
}
# Дополнительный, более ранний splash screen уровня ядра Linux,
# показываемый ДО старта самого Qt-приложения (через fbsplash или аналогичный механизм) —
# актуально для устройств, где даже запуск самого процесса приложения занимает заметное время

# /boot/cmdline.txt (для платформ типа Raspberry Pi)
console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 fsck.repair=yes rootwait quiet splash

Пояснения к коду

Структура main.cpp показывает приём «двухэтапной загрузки»: сначала создаётся и показывается максимально простой splashEngine с минимальным QML без сложной логики, а основной, «тяжёлый» QQmlApplicationEngine с реальным интерфейсом приложения создаётся через QTimer::singleShot(0, ...) — это откладывает более долгую инициализацию ровно на один цикл обработки событий, давая возможность сначала отрисовать и показать пользователю splash screen, прежде чем начинается потенциально долгая загрузка основного интерфейса.

Флаг Qt.SplashScreen для Window в QML убирает стандартные декорации окна (заголовок, рамки), что более уместно для экрана загрузки на embedded-устройстве без оконного менеджера. Дополнительный пример с quiet splash в параметрах загрузки ядра Linux показывает, что для действительно быстрого восприятия пользователем процесса загрузки иногда нужен ещё более ранний экран — показываемый системой до того, как даже начинается запуск пользовательских процессов, что выходит за пределы того, что может контролировать сам Qt, но важно для общего восприятия скорости загрузки устройства.

Подводные камни

  • Splash screen, который сам требует заметного времени на инициализацию (например, если SplashScreen.qml случайно тянет за собой тяжёлые QML-модули, используемые также основным интерфейсом) — теряет смысл, если показ «быстрого» экрана загрузки сам занимает значительное время; splash screen должен быть архитектурно изолирован от основного приложения и использовать минимальный набор зависимостей.
  • Splash screen, создающий ложное ощущение быстрой загрузки, в то время как реальная инициализация (подключение к базе данных, сети) либо не показывает прогресс вовсе, либо завершается с ошибкой без явного сообщения пользователю — простое статичное изображение без какой-либо индикации прогресса может быть воспринято как «зависшее» устройство, если основная инициализация занимает заметно больше пары секунд; для более долгих процессов стоит показывать хотя бы простой индикатор активности, а не статичную картинку без обратной связи.
  • Игнорирование оптимизации более ранних этапов загрузки системы (ядро Linux, системные сервисы) в попытке решить проблему медленного старта исключительно на уровне Qt-приложения — если основная задержка происходит до запуска самого процесса приложения (долгая инициализация файловой системы, ожидание сетевых сервисов при старте), splash screen внутри Qt-приложения просто не успеет показаться достаточно рано, чтобы скрыть эту часть задержки; для действительно быстрого старта нужна оптимизация всей цепочки, а не только кода самого приложения.
  • Несогласованность между splash screen и реальным временем готовности приложения к использованию. Если splash screen скрывается сразу после создания основного QQmlApplicationEngine, но до того, как реальный интерфейс полностью отрисован и интерактивен (например, ещё продолжается загрузка данных из сети для первого экрана), пользователь может увидеть промежуточное, не до конца готовое состояние интерфейса — переключение со splash screen на основной интерфейс должно происходить только когда основной интерфейс действительно полностью готов к взаимодействию, а не просто создан как объект.