Клиентский API RuntimeManager
RuntimeManager — это компонент в программном обеспечении, который отвечает за управление временем жизни приложения и фоновых задач. Он содержит набор функций для управления жизненным циклом приложения, включая запуск и остановку выполнения программы.
Не рекомендуется использовать опцию экспорта интерфейсов D-Bus, поскольку она позволяет запускать приложение напрямую. Предлагаемая альтернатива: интенты.
Через RuntimeManager приложение имеет возможность создавать фоновые задачи, независимые от жизненного цикла приложения. Он также предоставляет механизмы взаимодействия приложений, например, интенты или обработка URI.
Также RuntimeManager обеспечивает возможность клиентам отслеживать жизненный цикл приложений и сервисов в среде выполнения.
D-Bus-интерфейс Intents предназначен для работы с интентами.
- Среда приложения
- Фоновые задачи для приложений
- Условия исполнения
- Взаимодействие с приложениями
- Библиотека и API
- Управление фоновой задачей
- Вызов интента
- Обработка интентов
Среда приложения
RuntimeManager установит все переменные окружения, необходимые для правильного выполнения приложения, включая:
AURORA_APP_ID
— содержит идентификатор (<org-name>.<app_name>
) текущего приложения.AURORA_TASK_ID
— идентификатор фоновой задачи. Если один и тот же двоичный файл используется как для приложения, так и для его фоновых задач, эту переменную можно использовать, чтобы понять, в каком режиме выполняется процесс.AURORA_TASK_FD
— файловый дескриптор сокета-прослушивателя. Приложение или другие фоновые задачи могут подключаться к этому сокету для обмена данными.AURORA_APP_INSTANCE_ID
— содержит идентификатор приложения в сочетании с числовым идентификатором экземпляра (<org-name>.<app_name>.<instance_id>
), где значениеinstance_id
формируется какinstance_<уникальное число>
, для приложений с несколькими экземплярами; для приложений с одним экземпляром это будет то же самое, что иAURORA_APP_ID
. Только в некоторых случаях может понадобиться передатьAURORA_APP_INSTANCE_ID
экземпляра приложения, которое вручную запросило задание.
ID приложения и ID экземпляра приложения
Для поддержки запуска нескольких экземпляров приложения RuntimeManager использует
понятие "ID экземпляра приложения",
который представляет собой ID приложения.
ID приложения должен быть уникальным для всех экземпляров данного приложения.
Например, быть дополненным суффиксом .instance_<число>
.
Это необходимо, чтобы разрешить каждому из экземпляров регистрировать имя на шине D-Bus для получения сообщения от менеджера.
RuntimeDispatcher API
RuntimeDispatcher API основан на Qt API для вызова и регистрации интентов.
Фоновые задачи для приложений
Это фоновые процессы, которые необходимы приложениям для обеспечения их собственной функциональности. Они необходимы для следующих типов задач:
- периодические задачи — запускаются RuntimeManager с интервалами, определёнными приложением;
- запланированные задачи — запускаются RuntimeManager в определённое приложением время;
- управляемая событиями задача — задача будет запущена RuntimeManager при выполнении определённых условий. Если условия больше не выполняются, задача может быть остановлена или заморожена;
- рабочая задача — запускается по явному запросу приложения.
Во всех случаях они должны быть объявлены в desktop-файле
приложения путём добавления раздела:
[X-Task <TASK_ID>]
Type=[periodic|worker|location|push|<more-domain-specific-events>]
# Строка MimeType позволяет дополнительно добавлять обработчики для доменов у приложений
# Например, данное поле позволяет добавить приложение как обработчик домена qr.nspk.ru
# Тогда при переходе в браузере на https://qr.nspk.ru пользователю будет предложено перейти в приложение,
# которое зарегистрировало себя как обработчик данного домена (предполагается, что приложение,
# добавляя такой обработчик, действительно знает, как обрабатывать данный домен).
# Данный механизм работает для всех доменов.
MimeType=x-url-handler/qr.nspk.ru;...
# В более общем случае это поле может быть использовано, когда приложение регистрирует себя обработчиком
# одного типа файлов, например image/jpeg.
# Cписок наиболее часто используемых Mime-типов можно найти здесь:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types.
MimeType=image/jpeg;...
# Строка Exec может быть опущена, и в этом случае будет запущен
# тот же исполняемый файл, что и приложение.
Exec=/usr/bin/runner
# Строка разрешений может быть опущена, и в этом случае задача будет иметь
# только базовый профиль задачи
Permissions=Internet;...
# Подробнее о разделе Conditions в разделе "Условия исполнения"
Conditions=idle
Периодические задачи
Эти задачи используются для выполнения некоторой (короткой) работы через определённые промежутки
времени.
В desktop-файле
должны быть указаны максимальные временные рамки, в течение которых задача будет
выполняться, но расписание также может быть скорректировано приложением или самой задачей
при условии, что указанное расписание не должно предоставлять процессу более широкий график, чем
тот, который указан в desktop-файле
(это позволяет менеджеру пакетов проверить desktop-файл
на предмет злоупотреблений и может быть использовано для того, чтобы дать пользователю
представление о том, насколько ресурсоёмкой является задача).
Задача не должна делать никаких предположений о механизме, который будет использоваться для её запуска: это может быть то, что процесс прерывается и перезапускается при каждом "тике" расписания, или что один и тот же процесс замораживается/размораживается в соответствии с расписанием.
Можно указать условия, препятствующие запуску задачи, когда она не сможет выполнить свою работу.
Условия исполнения
Разработчики приложений могут добавить строку "Conditions" в свой файл desktop, чтобы указать, что задача должна выполняться только при определённых условиях.
Например:
# Двоеточие можно использовать для указания дополнительного условия, например "internet:wifi"
Conditions=<condition1>;<condition2>;<condition3>
Условие AND-ed (&&) используется для комбинирования двух или более условий и означает, что задача будет выполнена только при соблюдении всех условий.
Список поддерживаемых условий:
-
internet — выполняется при наличии подключения к Интернету. Доступны следующие дополнительные условия:
- Wi-Fi — подключение осуществляется по сети Wi-Fi;
- ethernet — подключение осуществляется через адаптер ethernet;
- wifi-or-ethernet;
- cellular — подключение осуществляется по сети сотовой связи;
-
idle — значение true, когда устройство находится в режиме ожидания (пользователь не использует его активно);
-
charging — значение true, когда устройство заряжается;
-
battery-not-low — батарея не разряжена.
Взаимодействие с приложениями
Схемы URI
Нет никаких ограничений на то, какие URI могут быть отправлены в уведомлении, при условии, что их размер не превышает 2048 байт.
С другой стороны, приложения, желающие обрабатывать уведомления, могут зарегистрироваться в качестве обработчиков только для следующих схем URI:
- любой схемы, одобренной IANA, со статусом "постоянный" или "временный";
- любая схема, начинающаяся с "aurora-<название_организации>.". Это позволяет приложениям определять свою собственную схему, гарантируя при этом отсутствие конфликтов с другими приложениями.
Доступные методы IPC
Приложение может взаимодействовать со своей фоновой задачей с помощью следующих методов IPC:
- обычные файлы, поскольку приложение и задача совместно используют общий вид файловой системы;
- сокеты с поддержкой файлов. Рекомендуется, чтобы сокет обслуживался фоновой задачей, поскольку приложение может быть закрыто/заморожено в любой момент;
- разделяемая память (в POSIX, то есть
shm_open()
иfriends
), но только в том случае, если имя области SHM начинается с идентификатора приложения; - безымянные семафоры (
sem_init()
); - именованные семафоры, если их название начинается с идентификатора приложения;
- D-Bus: фоновые задачи могут вызывать методы в приложении (приложение регистрирует применяемые методы как имя службы). Важно, что вызов методов не приведёт к автоматическому запуску приложения, если оно не запущено.
Библиотека и API
Runtime manager предоставляет библиотеку, чтобы запускать интенты и фоновые задачи:
- чтобы запускать фоновые задачи, используется класс
Task
:- для периодических задач есть класс
Schedule
, через который описывается расписание;
- для периодических задач есть класс
- если разработчик хочет использовать один и тот же бинарный файл и для приложения, и для фоновой
задачи, методы
onApplicationStarted
иonTaskStarted
в классеRuntimeDispatcher
помогают определить, если программа запускалась как приложение или как фоновая задача.
Управление фоновой задачей
Данный фрагмент кода иллюстрирует создание и управление задачей в фоновом режиме,
а также отображение QML-интерфейса с помощью RuntimeDispatcher
.
#include <RuntimeManager/RuntimeDispatcher>
#include <RuntimeManager/Task>
int main(int argc, char *argv[])
{
QGuiApplication *app = Aurora::Application::application(argc, argv);
app->setOrganizationName(QStringLiteral("ru.omp"));
app->setApplicationName(QStringLiteral("RMWorkerTaskExample"));
qDebug() << "Started with params" << app->arguments();
RuntimeDispatcher *dispatcher = RuntimeDispatcher::instance();
dispatcher->onTaskStarted("Writer", [app](const QString & /*taskID*/) {
// We are the background task
QString filePath = Aurora::Application::cacheDir().filePath(
"worker.txt");
QFile workerFile(filePath);
if (!workerFile.open(QIODevice::WriteOnly | QIODevice::Append)) {
qWarning() << "Could not open worker file";
QCoreApplication::exit(EXIT_FAILURE);
return;
}
workerFile.write("\n=====================================\n\n");
for (int i = 0; i < 1000; i++) {
QDateTime now = QDateTime::currentDateTime();
workerFile.write(now.toString().toUtf8() + '\n');
workerFile.flush();
QThread::sleep(1);
}
QCoreApplication::exit(EXIT_SUCCESS);
});
dispatcher->onApplicationStarted([app]() {
Task task("Writer");
task.withArguments({"--worker"})
.withStartDelay(10)
.withMaximumRunningTime(45)
.withPriority(10)
.start();
QString path = QLatin1String("MainPage.qml");
QQuickView *view = Aurora::Application::createView();
view->setSource(Aurora::Application::pathTo(path));
view->show();
view->setTitle("RMWorkerTaskExample");
});
return app->exec();
}
Вызов интента
В данном фрагменте кода происходит вызов и обработка интентов Start
и OpenURI
с использованием Invoker
.
#include <RuntimeManager/IntentsInvoker>
namespace {
const QString intentsHintPreferredHandler = QStringLiteral("preferredHandler");
const QString intentsIntentNameStart = QStringLiteral("Start");
const QString intentsIntentNameOpenURI = QStringLiteral("OpenURI");
}
namespace Intent {
IntentDefault::IntentDefault(QObject* parent)
: QObject(parent)
{
connect(
&IntentsInvoker::instance(),
&Invoker::IntentsInvoker::errorInfo,
this,
&IntentDefault::errorInfo,
Qt::QueuedConnection
);
}
void IntentDefault::registerQmlType()
{
qmlRegisterType<IntentDefault>("ru.auroraos.IntentDefault", 1, 0, "IntentDefault");
}
void IntentDefault::invokeStart(const QString& binName)
{
QJsonObject hints = {
{intentsHintPreferredHandler, binName},
};
QJsonObject params = {};
IntentsInvoker::instance().invoke(intentsIntentNameStart, hints, params);
}
void IntentDefault::invokeOpenURI()
{
QJsonObject hints = {};
QJsonObject params = {
{"uri", "tel:+79996660013"},
};
IntentsInvoker::instance().invoke(intentsIntentNameOpenURI, hints, params);
}
}
Обработка интентов
Если приложение способно обрабатывать интенты, то это должно быть объявлено в desktop-файле:
Intents=<intent-name>[/<detail>];<intent-name>...
# Пример:
Intents=X-TextToSpeech;X-SpeechToText
Пример регистрации интента:
auto *dispatcher = RuntimeManager::RuntimeDispatcher::instance();
dispatcher->registerIntent(QStringLiteral("X-MyIntent"),
[&dispatcher](const QJsonObject ¶ms,
const RuntimeDispatcher::HandlerCb &reply) {
// Сюда следует поместить код для обработки
QJsonObject response = { ... };
// Отправить данные ответа, используя обратный вызов, который получен в качестве параметра reply метода-обработчика.
// Этот обратный вызов также может быть вызван асинхронно
reply(response);
});
После того, как выполнены все важные действия по регистрации интентов, можно зарегистрировать службы D-Bus:
Error error = dispatcher->startService();
if (error)
return EXIT_FAILURE;
Интент Start
всегда регистрируется неявно, как и интент HandleURI
, если приложение
объявляет поддержку некоторых типов MIME.
Интент Start
:
Явно зарегистрирован? | Единичный экземпляр? | Вызов интента Start |
---|---|---|
Нет | Нет | Порождение invoker |
Нет | Да | Порождение invoker --single-instance |
Да | Нет | Порождение invoker + Start в D-Bus |
Да | Да | Порождение invoker --single-instance + Start в D-Bus |
Интент OpenURI
:
Явно зарегистрирован? | Зарегистрирован типы MIME? | Единичный экземпляр? | Вызов интента OpenURI |
---|---|---|---|
Нет | Нет | Да или Нет | Не вызывается |
Нет | Да | Да или Нет | Порождение invoker ... $URI |
Нет | Да | Да | Ошибка валидации |
Да | Нет | Да или Нет | Ошибка валидации |
Да | Да | Да или Нет | Порождение invoker [--single-instance] + HandleURI в D-Bus |
Если интенты Start
и OpenURI
были явно регистрированы через API,
тогда их вызов будет осуществляться через callback
, указанный в registerIntent
;
если нет, URI, переданные во время вызова метода OpenURI
данные,
будут передаваться приложению как параметры запуска.