Сервисы Android
Начиная с Qt 5.7, вы можете создавать сервисы Android с помощью Qt. Сервис — это компонент, работающий в фоновом режиме, поэтому у него нет пользовательского интерфейса. Он полезен для выполнения длительных операций, таких как регистрация GPS, ожидание уведомлений из социальных сетей и т. д. Сервис будет продолжать работу даже после выхода приложения, которое его запустило.
Сборка сервиса
Для начала создайте каталог пакета Android, как указано в Qt Creator: Развёртывание приложений на устройствах Android. Этот каталог содержит файл AndroidManifest.xml. Внутри каталога пакета создайте каталог src, где будут созданы все ваши Java-пакеты и классы.
Создание класса сервиса
Вы можете создать сервис, расширив класс QtService или Android: Service до своего Java-класса. В зависимости от того, хотите ли вы использовать функции Qt в своём сервисе или вызывать функции нативного C++ из Java, вам нужно расширить либо QtService, либо Service. Давайте начнём с простого сервиса, как показано ниже:
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.qtproject.qt5.android.bindings.QtService;
public class QtAndroidService extends QtService
{
private static final String TAG = "QtAndroidService";
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Creating Service");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "Destroying Service");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int ret = super.onStartCommand(intent, flags, startId);
// Do some work
return ret;
}
} Запуск сервиса
Android позволяет запускать сервисы по требованию или при загрузке. Вы можете делать и то, и другое с помощью Qt.
Запуск сервиса по требованию
Вы можете запустить сервис следующими способами:
- Непосредственно из C++ с помощью QAndroidIntent и QAndroidJniObject, создав сервис Intent и вызвав метод основного Activity приложения startService():
QAndroidIntent serviceIntent(QtAndroid::androidActivity().object(), "org/qtproject/example/qtandroidservice/QtAndroidService"); QAndroidJniObject result = QtAndroid::androidActivity().callObjectMethod( "startService", "(Landroid/content/Intent;)Landroid/content/ComponentName;", serviceIntent.handle().object()); - Запустить сервис, вызвав Java-метод. Самый простой способ — создать статический метод в вашем классе сервиса:
public static void startQtAndroidService(Context context) { context.startService(new Intent(context, QtAndroidService.class)); }Затем вы можете вызвать его из C++ с помощью следующего вызова JNI:
QAndroidJniObject::callStaticMethod<void>( "org/qtproject/example/qtandroidservice/QtAndroidService", "startQtAndroidService", "(Landroid/content/Context;)V", QtAndroid::androidActivity().object());
Запуск сервиса при загрузке
Для запуска сервиса при загрузке вам необходим BroadcastReceiver.
Создайте пользовательский Java-класс:
public class QtBootServiceBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent startServiceIntent = new Intent(context, QtBootServiceBroadcastReceiver.class);
context.startService(startServiceIntent);
}
} Добавьте следующее uses-permission в тело раздела <manifest> в файле AndroidManifest.xml.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
Также добавьте определение receiver в тело раздела <application>:
<receiver android:name=".QtBootServiceBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver> Примечание: Чтобы запустить сервис при загрузке, он должен быть определён как отдельный процесс, см. Сервис в отдельном процессе.
Управление сервисом в AndroidManifest.xml
Для использования сервиса в приложении Android необходимо объявить его в файле AndroidManifest.xml. Начнём с добавления раздела сервиса:
- При расширении
Service, просто объявите раздел сервиса как обычный Android-сервис. Добавьте следующее в раздел<application>:<service android:name=".QtAndroidService"> <!-- Background running --> <meta-data android:name="android.app.background_running" android:value="true"/> <!-- Background running --> </service>Таким образом, сервис будет запускаться в том же процессе, что и
QtActivity, что позволяет использовать нативные вызовы C++ из кода Java. Вы можете запустить его в отдельном процессе, но в этом случае вы не сможете использовать никакие нативные вызовы для взаимодействия, поскольку библиотеки Qt не загружаются для этого процесса. Для запуска в отдельном процессе добавьте это в тег сервиса:android:process=":qt_service"
- При расширении
QtService, вам нужно объявить другие элементы для загрузки всех необходимых библиотек Qt, в основном тех же, что и в разделе<activity>дляQtActivity. Добавьте следующее:<service android:process=":qt_service" android:name=".QtAndroidService"> <meta-data android:name="android.app.lib_name" android:value="service"/> <meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/> <meta-data android:name="android.app.repository" android:value="default"/> <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/> <meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/> <!-- Deploy Qt libs as part of package --> <meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/> <!-- Run with local libs --> <meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/> <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/> <meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/> <meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/> <meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/> <!-- Run with local libs --> <!-- Background running --> <meta-data android:name="android.app.background_running" android:value="true"/> <!-- Background running --> </service>
Примечание: Убедитесь, что определены следующие параметры для запуска сервиса в фоновом режиме:
<meta-data android:name="android.app.background_running" android:value="true"/>
Существуют несколько вариантов объявления сервисов. Некоторые из них уже используются в предыдущем фрагменте манифеста. В зависимости от вашего случая, запустите сервис либо в том же процессе, что и QtActivity, либо в отдельном процессе.
Сервис в том же процессе, что и QtActivity
Для запуска сервиса в том же процессе, что и QtActivity, объявите заголовок сервиса следующим образом:
<service android:name=".QtAndroidService">
Сервис в отдельном процессе
Для запуска сервиса в отдельном процессе объявите заголовок сервиса следующим образом:
<service android:process=":qt_service" android:name=".QtAndroidService">
Qt загружает файл .so , определённый в android.app.lib_name meta-data, и вызывает функцию main() со всеми аргументами, установленными в android.app.arguments meta-data. При запуске в отдельном процессе вы можете запустить сервис, используя либо тот же файл lib, что и основной Activity, либо отдельный файл lib.
Использование того же файла .so lib
Использование того же файла lib .so , что и у основного Activity, означает, что сервис будет использовать ту же точку входа с дополнительным аргументом для различения его от основного Activity. Вы можете обрабатывать выполнение вашего приложения в функции main() в зависимости от предоставленных аргументов. Добавьте следующее объявление аргумента в тело вашего сервиса:
<!-- Application arguments --> <meta-data android:name="android.app.arguments" android:value="-service"/> <!-- Application arguments -->
Затем убедитесь, что служба android.app.lib_name совпадает с основным активностью, добавьте следующее:
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
При использовании одного и того же файла библиотеки .so, функция приложения main() выполняется дважды: один раз для запуска основной активности и второй раз для запуска службы. Таким образом, вы должны обработать каждое выполнение в соответствии с предоставленным аргументом. Один из способов достижения этого — следующим образом:
if (argc <= 1) {
// code to handle main activity execution
} else if (argc > 1 && strcmp(argv[1], "-service") == 0) {
qDebug() << "Service starting with from the same .so file";
QAndroidService app(argc, argv);
return app.exec();
} else {
qWarning() << "Unrecognized command line argument";
return -1;
} Использование отдельного файла библиотеки .so
В этом случае вам необходимо создать подпроект с шаблоном lib, который предоставляет другой исполняемый файл для службы. Пример проекта .pro:
TEMPLATE = lib
TARGET = service
CONFIG += dll
QT += core androidextras
SOURCES += \
service_main.cpp
HEADERS += servicemessenger.h В service_main.cpp может быть следующее:
#include <QDebug>
#include <QAndroidService>
int main(int argc, char *argv[])
{
qWarning() << "Service starting from a separate .so file";
QAndroidService app(argc, argv);
return app.exec();
} Определите android.app.lib_name для службы в AndroidManifest.xml:
<meta-data android:name="android.app.lib_name" android:value="service"/>
Взаимодействие со службой
Qt для Android предлагает различные методы межпроцессного взаимодействия (IPC), чтобы взаимодействовать с Android-службами. В зависимости от структуры проекта вы можете использовать либо вызовы нативного кода C++ из Java-службы, либо Android BroadcastReceiver.
Вызовы нативного кода C++ из Java-службы
Это может работать со службами, выполняющимися в том же процессе, что и QtActivity, и даже если Service расширена. Для получения дополнительной информации см. Вызов функций QML/C++ из Java-кода.
Использование Android BroadcastReceiver
Android BroadcastReceiver позволяет обмениваться сообщениями между системой Android, приложениями, активностями и службами. Аналогично другим функциям Android, Qt может использовать BroadcastReceiver для обмена сообщениями между QtActivity и вашей службой. Начнем с логики отправки сообщения из вашей службы. Добавьте следующее в реализацию вашей службы, которая вызывает sendBroadcast():
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int ret = super.onStartCommand(intent, flags, startId);
Intent sendToUiIntent = new Intent();
sendToUiIntent.setAction(ActivityUtils.BROADCAST_CUSTOM_ACTION);
sendToUiIntent.putExtra("message", "simple_string");
Log.i(TAG, "Service sending broadcast");
sendBroadcast(sendToUiIntent);
return ret;
} Затем вам необходимо создать и зарегистрировать BroadcastReceiver из основной активности Qt. Самый простой способ — создать пользовательский класс с методом и реализовать всю эту логику на Java. В следующем примере служба отправляет сообщение "simple_string" в Qt, вызвав нативный метод sendToQt():
public class ServiceBroadcastUtils {
private static native void sendToQt(String message);
private static final String TAG = "ActivityUtils";
public static final String BROADCAST_CUSTOM_ACTION = "org.qtproject.example.qtandroidservice.broadcast.custom";
public void registerServiceBroadcastReceiver(Context context) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BROADCAST_CUSTOM_ACTION);
context.registerReceiver(serviceMessageReceiver, intentFilter);
Log.i(TAG, "Registered broadcast receiver");
}
private BroadcastReceiver serviceMessageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "In OnReceive()");
if (BROADCAST_CUSTOM_ACTION.equals(intent.getAction())) {
String message = intent.getStringExtra("message");
sendToQt(data);
Log.i(TAG, "Service sent back message to C++: " + message);
}
}
};
} Для получения дополнительной информации о работе с вызовами нативного кода см. Вызов функций QML/C++ из Java-кода.
Чтобы использовать всё это, запустите службу, как показано в Запуск службы, а затем зарегистрируйте BroadcastReceiver, вызвав метод registerServiceBroadcastReceiver():
QAndroidJniEnvironment env;
jclass javaClass = env.findClass("org/qtproject/example/qtandroidservice/ActivityUtils");
QAndroidJniObject classObject(javaClass);
classObject.callMethod<void>("registerServiceBroadcastReceiver",
"(Landroid/content/Context;)V",
QtAndroid::androidContext().object()); Использование Qt Remote Objects
Qt Remote Objects предлагает простой способ обмена API между процессами Qt. Основная концепция состоит в том, чтобы сервер находился в процессе службы, а реплика — в приложении Qt, и затем эти две части могут обмениваться данными друг с другом, используя сигналы и слоты.
Подготовка реплики
Рассмотрим пример службы с отдельным файлом библиотеки .so. Определите файл .rep, который определяет наш класс связи:
class ServiceMessenger {
SLOT(void ping(const QString &message));
SIGNAL(pong(const QString &message));
} Затем определите класс в подпроекте службы как servicemessenger.h:
#include "rep_servicemessenger_source.h"
class ServiceMessenger : public ServiceMessengerSource {
public slots:
void ping(const QString &name) override {
emit pong("Hello " + name);
}
}; Затем добавьте файл .rep в файлы основного приложения и службы .pro, в основном приложении:
QT += remoteobjects REPC_REPLICA += servicemessenger.rep
И в подпроекте службы:
QT += remoteobjects REPC_SOURCE += servicemessenger.rep
Подключение источника и реплики
Определите узел источника Qt Remote Objects в функции main() подпроекта службы:
#include "servicemessenger.h"
#include <QDebug>
#include <QAndroidService>
int main(int argc, char *argv[])
{
qWarning() << "QtAndroidService starting from separate .so";
QAndroidService app(argc, argv);
QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:replica")));
ServiceMessenger serviceMessenger;
srcNode.enableRemoting(&serviceMessenger);
return app.exec();
} Затем, в функции main() приложения, подключитесь к узлу источника:
QRemoteObjectNode repNode;
repNode.connectToNode(QUrl(QStringLiteral("local:replica")));
QSharedPointer<ServiceMessengerReplica> rep(repNode.acquire<ServiceMessengerReplica>());
bool res = rep->waitForSource();
Q_ASSERT(res);
QObject::connect(rep.data(), &ServiceMessengerReplica::pong, [](const QString &message){
qDebug() << "Service sent: " << message;
});
rep->ping("Qt and Android are friends!"); В этом примере сообщение отправляется из процесса основного приложения в службу. Служба отвечает тем же сообщением, которое печатается в журнале отладки logcat.
Примечание: тот же метод можно использовать при использовании одного и того же файла библиотеки .so. Для получения дополнительной информации см. Использование одного и того же файла .so библиотеки.
Использование QAndroidBinder
Хотя использование Qt Remote Objects для связи является кроссплатформенным решением, класс QAndroidBinder специфичен для Android. QAndroidBinder — это вспомогательный класс, реализующий наиболее важные методы в Android: Binder. Он позволяет отправлять объекты QByteArray или QVariant между процессами.
Примечание: Qt для Android имеет ограничение, вынуждающее выполнять только одну службу за раз при запуске нескольких служб в одном процессе. Поэтому рекомендуется запускать каждую службу в собственном процессе. Для получения дополнительной информации см. QTBUG-78009.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.15/android-services.html