MyTetra Share
Делитесь знаниями!
Как создать Android-сервис с использованием Qt
Время создания: 15.01.2019 19:01
Автор: Xintrea
Текстовые метки: c++, java, qt, Android, service, андроид, сервис
Раздел: Компьютер - Программирование - Java - Java в Android
Запись: xintrea/mytetra_syncro/master/base/1547568115kz829ftfkd/text.html на raw.github.com

Оригинальное название статьи: 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.64
Яндекс индекс цитирования