MyTetra Share
Делитесь знаниями!
Как создать Android-сервис с использованием Qt
15.01.2019
19:01
Автор: Xintrea
Текстовые метки: c++, java, qt, Android, service, андроид, сервис
Раздел: Компьютер - Программирование - Java - Java в Android

Оригинальное название статьи: Qt on Android: How to create an Android service using Qt



Начиная с Qt 5.7, в данный фреймверк была добавлена возможность создавать Android-сервисы. В этой статье мы рассмотрим, как запускать сервисы и как организовать взаимодействие между двумя сервисами.


Перед тем как мы начнем, следует сделать очень важное предупреждение об ресурсах (CPU, память и т.д.) используемых сервисом. Так как сервис запускается в фоновом режиме, и обычно работает очень длительное время, то при разработке следует уделить особое внимание качеству кода, чтобы сервис не растрачивал ресурсы системы и не тратил попусту заряд батареи мобильного устройства.



Важное замечание для Qt 5.10 и выше

Начиная с релиза Qt 5.10, необходимо использовать QAndroidService вместо QCoreApplication в реализации серверной части сервиса.


Начало


Шаг 1. Расширение QtService


Каждый отдельный сервис, создаваемый в Android через Qt, должен иметь свой Java-класс, расширенный от специального Java-класса QtService. Поэтому первым шагом является создание такого сервиса:



// java file goes in android/src/com/kdab/training/MyService.java

package com.kdab.training;

import org.qtproject.qt5.android.bindings.QtService;

 

public class MyService extends QtService

{

}



Шаг 2. Добавление секции service в файл AndroidManifest.xml


Следующим шагом является добавление серции service в файл AndroidManifest.xml. Для этого сначала нужно скопировать и вставить шаблон из https://wiki.qt.io/AndroidServices в файл AndroidManifest.xml, а затем прописать в атрибут android: name строку с именем вашего класса. Как это выглядит, показано в следующем фрагменте:



<application ... >

  <!-- .... -->

  <service android:process=":qt" android:name=".MyService">

  <!-- android:process=":qt" is needed to force the service to run on a separate

                                                        process than the Activity -->

 

    <!-- .... -->

 

    <!-- Background running -->

    <meta-data android:name="android.app.background_running" android:value="true"/>

    <!-- Background running -->

  </service>

  <!-- .... -->

</application>



ВНИМАНИЕ: каждый отдельный Сервис или Активность Qt ДОЛЖНЫ выполняться в своем собственном процессе! Поэтому для каждого сервиса необходимо устанавливать разные значения атрибута android: process.


Прим. переводчика: в каждом приложении может быть создано несколько сервисов, соответственно, в пределах тега <application ...> может находиться несолько тегов <service ...>.



Шаг 3. Как запустить сервис?

Теперь нужно решить, как запустить сервис. Есть два способа сделать это:

  • по запросу, по требованию
  • во время загрузки устройства

Ниже описано как сделать оба варианта.



Запуск сервиса по требованию


Это наиболее распространенный способ запуска сервиса. Чтобы запустить службу, вам просто нужно вызвать метод Context.startService(Intent intent). Самый простой способ сделать это - добавить статический метод в MyService:



// java file goes in android/src/com/kdab/training/MyService.java

package com.kdab.training;

import android.content.Context;

import android.content.Intent;

import org.qtproject.qt5.android.bindings.QtService;

public class MyService extends QtService

{

    public static void startMyService(Context ctx) {

        ctx.startService(new Intent(ctx, MyService.class));

    }

}



Затем просто вызовите его из Qt, чтобы запустить его:



QAndroidJniObject::callStaticMethod<void>("com/kdab/training/MyService",

                                          "startMyService",

                                          "(Landroid/content/Context;)V",

                                          QtAndroid::androidActivity().object());



Запуск сервиса во время загрузки устройства


Этот метод используется довольно редко и полезен ТОЛЬКО в тех случаях, когда вам действительно нужно запустить службу во время загрузки устройства, в противном случае рекомендуется запускать ее по требованию.


Сначала вам нужно добавить разрешение android.permission.RECEIVE_BOOT_COMPLETED в ваш файл AndroidManifest.xml:



<application ... >

 

  <!-- .... -->

  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

</application>



Затем в файл AndroidManifest.xml нужно добавить элемент получателя сигнала от системы о том, что загрузка устройства завершена (и можно запускать сервис):



<application ... >

    <!-- .... -->

    <receiver android:name=".MyBroadcastReceiver">

        <intent-filter>

            <action android:name="android.intent.action.BOOT_COMPLETED" />

        </intent-filter>

    </receiver>

    <!-- .... -->

</application>



И, наконец, нужно реализовать класс MyBroadcastReceiver, как показано в следующем фрагменте:



public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override

    public void onReceive(Context context, Intent intent) {

        Intent startServiceIntent = new Intent(context, MyService.class);

        context.startService(startServiceIntent);

    }

}



При создании данного получателя, он будет запускать сервис, реализация которого находится в классе MyService. Запуск будет происходить при приходе сообщения от системы android.intent.action.BOOT_COMPLETED, так как этот фильтр был прописан в файле манифеста.



Шаг 4. Где разместить код QtService?


Далее нужно решить, куда вы собираетесь поместить свой сервисный код. Фреймверк Qt и система сборки qmake поддерживает два варианта:

  • в том же файле .so, где находится и основное приложение
  • в отдельном файле .so

Ниже описаны оба варианта.


Один и тот же файл *.so для кода приложения и для кода сервиса

Поскольку будет один большой .so-файл, необходим способ узнать, как он будет работать: как Активность (то есть, как приложение пользователя) или как Сервис. Для этого вам просто нужно передать некоторые аргументы в вашу функцию main(). Файл AndroidManifest.xml позволяет легко сделать это:


<service ... >

    <!-- ... -->

    <!-- Application arguments -->

    <meta-data android:name="android.app.arguments" android:value="-service"/>

    <!-- Application arguments -->

    <!-- ... -->

</service>



Кроме того, надо убедиться, что установлены одинаковые значения метаданных android.app.lib_name для сервиса (сервисов) и для Активности самого приложения:



<service ... >

    <!-- ... -->

    <meta-data android:name="android.app.lib_name"

                android:value="-- %%INSERT_APP_LIB_NAME%% --"/>

    <!-- ... -->

</service>



Рекомендуется использовать этот метод компоновки сприложения в случае, если и само приложение, и сервис используют один и тот же большой кусок кода.



Отдельные .so файлы для приложения и сервисов


Второй вариант - создать отдельные файлы *.so для вашего приложения и для сервиса. Сначала вам нужно создать отдельный файл .pro для сервера:


TEMPLATE = lib

TARGET = server

CONFIG += dll

QT += core

SOURCES += \

    server.cpp


Таким образом будет создаваться so-файл с точкой входа в виде функции main():



#include <QDebug>

 

int main(int argc, char *argv[])

{

    qDebug() << "Hello from service";

    return 0

}



И последнее, что нужно сделать, это разрешить загрузку .so-файла в виде сервиса:



<service ... >

    <!-- ... -->

    <meta-data android:name="android.app.lib_name" android:value="server"/>

    <!-- ... -->

</service>




Внимание! Использование дальнейших рекомендаций под большим вопросом. Использование их приведет к привязке проекта к сырым технологиям, поддержка которых не гарантируется команией Qt.



Использование QtRemoteObject для коммуникации между процессами


Мы видели, как создавать и запускать сервисы Qt на Android, теперь давайте посмотрим, как установить связь между ними. Существует множество решений, но для любого проекта Qt я рекомендую вам использовать QtRemoteObject. Использование этого класса в Qt очень облегчает жизнь программиста!


QtRemoteObjects - это пробный модуль Qt, разрабатываемый при поддержке компании Ford, используемый для удаленного взаимодействия объектов между процессами/устройствами. Данный модуль:


  • удаленно экспортирует объекты QObject (свойства, сигналы и слоты)
    • удаленно экспортирует QAbstractItemModels
  • создает "репликанта" на стороне клиента, с которым вы можете взаимодействовать
  • repc генерирует файлы исходников и replica-файлы исходников (сервер и клиент) из файлов .rep
  • файл .rep - это IDL QtRemoteObjects (язык описания интерфейса)


Как вы можете видеть, эта технология очень специфична для Qt. Давайте посмотрим, как добавить её в свои проекты и как использовать её.



Получение QtRemoteObjects


Проект QtRemoteObjects находится здесь: http://code.qt.io/cgit/qt/qtremoteobjects.git/. Для его получания можно воспользоваться командами:


$ git clone git://code.qt.io/qt/qtremoteobjects.git

$ cd qtremoteobjects

$ ~/Qt/5.10.1/android_armv7/bin/qmake -r && make && make install



Если необходимо, нужно заменить путь ~/Qt/5.10.1/android_armv7 на вашу версию Qt и ваш Android ABI.



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


Использовать QtRemoteObjects довольно просто, нужно сделать несколько несложных шагов:


– Добавить QtRemoteObjects в ваш *.pro файл


# ...

QT += androidextras

QT += remoteobjects

# ...


– Создать .rep файл(ы)


class PingPong {

    SLOT(void ping(const QString &msg));

    SIGNAL(pong(const QString &msg));

}


– Добавить .rep файл(ы) в .pro-файл сервиса


# ...

REPC_SOURCE += pingpong.rep

# ...


– Добавить .rep файл(ы) в .pro файл клиента


# ...

REPC_REPLICA += pingpong.rep

# ...


– Сделать реализацию QtRemoteObjects на стороне сервера


#include <QAndroidService>

#include "rep_pingpong_source.h"

class PingPong : public PingPongSource {

public slots:

    // PingPongSource interface

    void ping(const QString &msg) override {

        emit pong(msg + " from server");

    }

};

int main(int argc, char *argv[])

{

    QAndroidService app(argc, argv);

    QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:replica")));

    PingPong pingPongServer;

    srcNode.enableRemoting(&pingPongServer);

    return app.exec();

}



Теперь можно немного проверить код. Сначала необходимо реализовать все интерфейсы .rep ( PingPongSource ), затем экспортировать объект PingPong с помощью enableRemoting.


– Релизация реплики QtRemoteObjects на стороне клиента


#include "rep_pingpong_replica.h"

// ....

    QRemoteObjectNode repNode;

    repNode.connectToNode(QUrl(QStringLiteral("local:replica")));

    QSharedPointer<PingPongReplica> rep(repNode.acquire<PingPongReplica>());

    bool res = rep->waitForSource();

    Q_ASSERT(res);

    QObject::connect(rep.data(), &PingPongReplica::pong, [](const QString &msg){

        qDebug() << msg;

    });

    rep->ping("Hello");

// ....



Давайте проверим код. Он должен:


  • использовать QRemoteObjectNode для подключения к QRemoteObjectHost
  • использовать QRemoteObjectNode:acquire, чтобы связать локальный объект с удаленным
  • использовать полученный объект как локальный (вызывные слоты, подключение к сигналам и т. д.)


Как видите, использовать Qt + QtRemoteObject (намного???) проще, чем Java-сервисы Android + AIDL.



Ограничения


  • Приложение и сервис должны выполняться в разных процессах
  • Невозможно (пока) использовать QtCreator, чтобы легко добавить раздел службы в файл AndroidManifest.xml, чтобы проверить QTCREATORBUG-16884
  • Невозможно (пока) использовать QtCreator для простой генерации подпроекта сервиса, проверьте QTCREATORBUG-16885
  • Невозможно (пока) увидеть журналы сервисов в QtCreator. Вам нужно будет использовать adb logcat чтобы увидеть лог, проверьте QTCREATORBUG-16887
  • Невозможно (пока, надеюсь) использовать сервис отладки в QtCreator. Для реализации этой функции потребуется некоторое время, QTCREATORBUG-16886


Пожалуйста, используйте приведенные выше идентификаторы багов для сообщений об ошибках в багтрекере Qt, чтобы проголосовать за доработку необходимых вам задач. Те, у которых больше голосов (обычно), выполняются первыми!


 
MyTetra Share v.0.52
Яндекс индекс цитирования