Spec-Zone.ru › Qt

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.qt.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 и QJniObject, создав Intent сервиса и вызвав метод основной активности приложения startService():
    auto activity = QJniObject(QNativeInterface::QAndroidApplication:context());
    QAndroidIntent serviceIntent(activity.object(),
                                 "org/qtproject/example/qtandroidservice/QtAndroidService");
    QJniObject result = activity.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:

    QJniObject::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, QtAndroidService.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>

Примечание: Android 8.0 ввёл некоторые ограничения на работу фоновых сервисов, что означает, что использование обычного класса Service может не сработать. Более подробную информацию см. в рекомендациях Android по использованию фоновых сервисов или JobIntentService.

Управление сервисом в 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.background_running" android:value="true"/>
    </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, что и основная активность, либо отдельный файл lib.

Использование одного и того же файла .so lib

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

В этом случае вам нужно иметь подпроект с шаблоном lib, который предоставляет другой исполняемый файл для сервиса. Пример проекта .pro:

TEMPLATE = lib
TARGET = service
CONFIG += dll
QT += core core-private

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 расширен.

Дополнительную информацию см. в примере Qt Android Notifier.

Использование Android BroadcastReceiver

Android BroadcastReceiver позволяет обмениваться сообщениями между Android-системой, приложениями, активностями и сервисами. Аналогично другим функциям Android, Qt может использовать broadcast-ресиверы для обмена сообщениями между 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;
}

Затем вам нужно создать и зарегистрировать broadcast-ресивер из основной активности 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);
            }
        }
    };
}

Чтобы использовать всё это, запустите ваш сервис, как показано в разделе Запуск сервиса, а затем зарегистрируйте broadcast-ресивер, вызвав метод registerServiceBroadcastReceiver():

QJniEnvironment env;
jclass javaClass = env.findClass("org/qtproject/example/qtandroidservice/ActivityUtils");
QJniObject classObject(javaClass);

classObject.callMethod<void>("registerServiceBroadcastReceiver",
                             "(Landroid/content/Context;)V",
                             QtAndroid::androidContext().object());

Использование Qt Remote Objects

Qt Remote Objects предлагает лёгкий способ обмена API между процессами Qt. Основная идея заключается в сервере в процессе сервиса и его реплике в Qt-приложении, которые могут обмениваться данными друг с другом с помощью сигналов и слотов.

Подготовка реплики

Рассмотрим пример сервиса с отдельным файлом .so lib. Определите файл .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 в файлы как основного приложения, так и подпроекта сервиса. В основном приложении:

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 lib. Более подробную информацию см. в разделе Использование одного и того же файла .so lib.

Использование QAndroidBinder

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-6.2/android-services.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API