Введение


Довольно часто, при реализации какой-либо логики в приложении, возникает потребность в срабатывании некоторой функции через определённый промежуток времени. Наиболее очевидным примером такой потребности является приложение таймера. Например, 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.

Комментарии (2)


  1. monah_tuk
    06.06.2018 19:58

    Сходу вопрос: на каком физическом телефоне гоняете Sailfish?


    1. osanwe Автор
      07.06.2018 07:36

      Jolla C и Inoi R7.