Введение
Довольно часто, при реализации какой-либо логики в приложении, возникает потребность в срабатывании некоторой функции через определённый промежуток времени. Наиболее очевидным примером такой потребности является приложение таймера. Например, cooktimer или Saildoro.
Как было сказано в одной из предыдущих статей, для добавления таймера в приложение на Sailfish OS можно использовать стандартный элемент
Timer или его C++ аналог — QTimer. Однако, по умолчанию, работа этих таймеров приостанавливается на долгих промежутках времени из-за того, что устройство может уйти в «сон». Как раз с этой проблемой и столкнулись разработчики двух приложений, упомянутых выше.В данной статье представлен стандартный для системы, но к сожалению недокументированный способ обработки такого поведения Sailfish OS.
Начальная точка
В качестве отправной точки будет рассматриваться «абстрактное» приложение для Sailfish OS, в котором требуется срабатывание некоторого функционала через длительный промежуток времени. При этом работа таймера описывается не в коде QML, а в классе на C++:
Header
class TimerWrapper : public QObject { Q_OBJECT public: // Конструктор и деструктор explicit TimerWrapper(QObject *parent = 0); ~TimerWrapper(); Q_INVOKABLE void start(int interval); // Метод для запуска таймера Q_INVOKABLE void stop(); // Метод для остановки таймера signals: void pomodoroFinished(int start, int end); // Сигнал остановки таймера void activeChanged(); // Сигнал смены состояния таймера private: QTimer *_timer; // Объект таймера int _startTime; // Время запуска таймера };
Source
#include "timerwrapper.h" /** * Конструктор таймера. */ TimerWrapper::TimerWrapper(QObject *parent) : QObject(parent) { _timer = new QTimer(this); // Создание объекта таймера _timer->setSingleShot(true); // Отключение автоматического возобновления таймера // После остановки таймера посылаются сигналы смены состояния и завершения работы connect(_timer, &QTimer::timeout, [=]() { emit activeChanged(); eemit pomodoroFinished(_startTime, QDateTime::currentDateTime().toMSecsSinceEpoch()); }); } /** * Деструктор таймера. */ TimerWrapper::~TimerWrapper() { delete _timer; _timer = nullptr; } /** * Метод для начала работы таймера. * @:param: interval - длительность работы таймера в миллисекундах */ void TimerWrapper::start(int interval) { _startTime = QDateTime::currentMSecsSinceEpoch(); // Сохранение времени начала _timer->start(interval); // Запуск таймера emit activeChanged(); // Сигнал о смене состояния таймера } /** * Метод для остановки таймера. */ void TimerWrapper::stop() { _timer->stop(); // Остановка таймера emit activeChanged(); // Сигнал о смене состояния }
Объект такого класса должен быть зарегистрирован в QML:
main.cpp
#ifdef QT_QML_DEBUG #include <QtQuick> #endif #include <QGuiApplication> #include <QQmlContext> #include <QQuickView> #include <QScopedPointer> #include <sailfishapp.h> #include "timerwrapper.h" int main(int argc, char *argv[]) { // Создание объекта приложения QScopedPointer<QGuiApplication> application(SailfishApp::application(argc, argv)); // Создание объекта для отображения интерфейса QScopedPointer<QQuickView> view(SailfishApp::createView()); // Создание объекта таймера QScopedPointer<TimerWrapper> timer(new TimerWrapper(view.data())); // Регистрация объекта таймера view->rootContext()->setContextProperty("timer", timer.data()); // Объявление пути к стартовому QML-файлу view->setSource(SailfishApp::pathTo("qml/harbour-application.qml")); // Отображение интерфейса view->show(); // Запуск приложения return application->exec(); }
При таком подходе, как упоминалось во введении, на длительных интервалах времени может наблюдаться приостановка работы таймера.
Решение
Первый вариант предотвращения засыпания таймера был предложен в почтовой рассылке разработчиков и прижился в приложении cooktimer. Здесь предлагается завести в явном виде дополнительный таймер, который раз в минуту вызывает D-Bus метод
req_display_cancel_blanking_pause, чтобы предотвратить засыпание устройства. Очевидно, что такая реализация неоптимальна и громоздка. Во-первых, при использовании такого подхода быстрее разряжается батарея устройства. Во-вторых, в проекте появляется второстепенный код, которого можно избежать.А избежать использования второстепенного кода можно потому, что Sailfish OS уже предоставляет два возможных решения поставленной проблемы: элементы
ScreenBlank и KeepAlive.Использование первого подхода подразумевает постоянно активный экран. Это рабочий, но прямолинейный подход, активно потребляющий заряд аккумулятора устройства. Таким образом, использовать его можно, но в ограниченном круге ситуаций.
import QtQuick 2.0 // Подключение модуля для поддержки стандартных элементов QML import Sailfish.Silica 1.0 // Подключение модуля для поддержки Sailfish OS UI import Sailfish.Media 1.0 // Подключение модуля для поддержки элемента ScreenBlank ApplicationWindow // Объявление главного окна приложения { initialPage: Component { FirstPage { } } // Объявление главной страницы приложения cover: Qt.resolvedUrl("cover/CoverPage.qml") // Объявление обложки приложения ScreenBlank { // Объявление элемента для предотвращения засыпания устройства id: screenBlank // Идентификатор для обращения suspend: true // Экран постоянно активирован } }
В свою очередь использование элемента
KeepAlive является более демократичным подходом. Он в меньшей степени потребляет заряд батареи, так как не держит экран устройства постоянно включенным, и в то же время, либо не даёт уйти устройству в глубокий сон, либо «будит» его в определённый момент времени, благодаря чему таймер будет продолжать работу и на долгих промежутках времени.import QtQuick 2.0 // Подключение модуля для поддержки стандартных элементов QML import Sailfish.Silica 1.0 // Подключение модуля для поддержки Sailfish OS UI import org.nemomobile.keepalive 1.1 // Подключение модуля для поддержки элемента KeepAlive ApplicationWindow // Объявление главного окна приложения { initialPage: Component { FirstPage { } } // Объявление главной страницы приложения cover: Qt.resolvedUrl("cover/CoverPage.qml") // Объявление обложки приложения KeepAlive { // Объявление элемента для предотвращения засыпания устройства id: keepAlive // Идентификатор для обращения enabled: true // Устройство не уходит в глубокий сон } }
Стоит заметить, что в принципе работы всех трёх упомянутых способах лежит регулярное обращение к системным методам D-Bus, что обсуждалось в одной из предыдущих статей.
Заключение
В рамках данной небольшой заметки описаны три возможных способа предотвращения глубокого засыпания устройства. Можно сделать вывод, что для фоновых задач (например, таймер) оптимально использовать элемент
KeepAlive, а если имеется необходимость постоянного отображения информации пользователю, то ScreenBlank.