Android サービス

Qt 5.7から、Qtを使ってAndroidサービスを作成できるようになりました。サービスはバックグラウンドで動作するコンポーネントで、ユーザーインターフェイスを持ちません。GPSのログを取ったり、ソーシャルメディアの通知を待ったりなど、長期的な処理を行うのに便利です。サービスは、それを起動したアプリケーションが終了しても実行され続けます。

サービスを組み立てる

始めるには、Qt Creatorの指示に従ってAndroidパッケージディレクトリを作成します:Qt Creator:Deploying Applications to Android Devices で説明されているように、Android パッケージディレクトリを作成します。このディレクトリにはAndroidManifest.xml ファイルが含まれます。packageディレクトリの中に、src ディレクトリを作成します。ここには、Javaパッケージとクラスがすべて作成されます。

サービス・クラスの作成

QtService 、またはAndroid.Serviceクラスを拡張して、サービスを作成できますサービスを作成するには、 または Android:Serviceクラスをあなたの Java クラスに拡張します。サービスでQtの機能を使用するか、JavaからネイティブのC++関数を呼び出すかによって、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を使っても、どちらも可能です。

オンデマンドでサービスを開始する

以下の方法でサービスを開始できます:

  • QAndroidIntentQJniObject 、サービス・インテントを作成し、アプリのメイン・アクティビティ・メソッドstartService()を呼び出すことで、C++から直接サービスを開始できます:
    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));
    }

    次のJNIコールを使用して、C++からこのメソッドを呼び出すことができます:

    const QJniObject context(QNativeInterface::QAndroidApplication::context());
    QJniObject::callStaticMethod<void>(
        "org/qtproject/example/qtandroidservice/QtAndroidService",
        "startQtAndroidService",
        "(Landroid/content/Context;)V",
        context.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);
    }
}

AndroidManifest.xml ファイルの<manifest> セクションの本文に、以下のuses-permission を追加します:

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

また、<application> セクションの本文にreceiver の定義を追加する:

<receiver android:name=".QtBootServiceBroadcastReceiver" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

注: Android 8.0では、バックグラウンド・サービスの実行にいくつかの制限が導入されたため、通常のService クラスを使用しても機能しない場合があります。詳細については、ForegroundサービスまたはJobIntentServiceのいずれかを使用するAndroidの推奨を参照してください。

AndroidManifest.xmlでサービスを管理する

サービスをAndroidアプリで使えるようにするには、AndroidManifest.xml ファイルで宣言する必要があります。まずは、サービス・セクションを追加するところから始めましょう:

  • Service を拡張する場合は、サービス・セクションを通常のAndroidサービスとして宣言するだけです。<application>
    <service android:name=".QtAndroidService" android:exported="true">
        <!-- Background running -->
        <meta-data android:name="android.app.background_running" android:value="true"/>
        <!-- Background running -->
    </service>

    こうすることで、サービスがQtActivity と同じプロセスで開始され、Java コードからネイティブ C++ 呼び出しを使用できるようになります。別のプロセスで実行することもできますが、その場合はQtライブラリーがロードされないため、通信にネイティブ・コールを使用できません。別プロセスで実行するには、serviceタグに次のように追加します:

    android:process=":qt_service"
  • QtService を拡張する場合、Qt に必要なすべてのライブラリをロードするための他の項目を宣言する必要があります。主に、<activity> のセクションと同じ項目をQtActivity に宣言します。以下を追加してください:
    <service android:process=":qt_service" android:name=".QtAndroidService" android:exported="true">
        <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" android:exported="true">

別プロセスでのサービス

専用プロセスでサービスを実行するには、サービスヘッダを次のように宣言します:

<service android:process=":qt_service" android:name=".QtAndroidService" android:exported="true">

Qt はandroid.app.lib_name meta-data で定義された.so ファイルをロードし、android.app.arguments meta-data で設定されたすべての引数でmain() 関数を呼び出します。別プロセスで実行する場合、メインアクティビティと同じ lib ファイルを使用するか、別の lib ファイルを使用してサービスを開始することができます。

同じ.so libファイルを使用する

メイン・アクティビティと同じ.so libファイルを使用することは、サービスがメイン・アクティビティと区別するための余分な引数を持つ同じエントリ・ポイントを使用することを意味します。提供された引数に従って、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() 関数は2回実行されます。1回目はメイン・アクティビティを開始し、2回目はサービスを開始します。したがって、提供された引数に従って、それぞれの実行を処理する必要があります。そのための1つの方法は以下の通りです:

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();
}

AndroidManifest.xml にサービス用のandroid.app.lib_name を定義します:

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

サービスとの通信

Qt for Androidは、Androidサービスと通信するための様々なプロセス間通信(IPC)メソッドを提供しています。プロジェクトの構造に応じて、Java ServiceからのネイティブC++コールまたはAndroid BroadcastReceiverを使用できます。

JavaサービスからのネイティブC++コール

これは、QtActivity と同じプロセスで実行されているサービスや、Service が拡張されている場合でも動作します。

詳細については、Qt Android Notifier Exampleを参照してください。

Android BroadcastReceiverの使用

Android BroadcastReceiverを使用すると、Androidシステム、アプリ、アクティビティ、およびサービス間でメッセージを交換できます。他の Android の機能と同様に、Qt でもブロードキャストレシーバーを使用して、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;
}

次に、Qtのメイン・アクティビティからブロードキャスト・レシーバーを作成し、登録する必要があります。最も簡単な方法は、メソッドを持つカスタムクラスを作成し、Javaですべてのロジックを実装することです。次の例では、ネイティブ・メソッドsendToQt() を呼び出すことで、サービスが Qt にメッセージ"simple_string" を送信します:

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);
            }
        }
    };
}

これらすべてを利用するには、Start the Service で示したようにサービスを開始し、registerServiceBroadcastReceiver() メソッドを呼び出してブロードキャスト・レシーバーを登録します:

QJniEnvironment env;
jclass javaClass = env.findClass("org/qtproject/example/qtandroidservice/ActivityUtils");
QJniObject classObject(javaClass);
const QJniObject context(QNativeInterface::QAndroidApplication::context());
classObject.callMethod<void>("registerServiceBroadcastReceiver",
                             "(Landroid/content/Context;)V",
                             context.object());

Qt リモートオブジェクトを使う

Qtリモートオブジェクトは、Qtプロセス間でAPIを共有する簡単な方法を提供します。主なコンセプトは、サービスプロセスにサーバーを用意し、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 ファイルをメインアプリケーションとサービス.pro ファイルの両方に追加します:

QT += remoteobjects
REPC_REPLICA += servicemessenger.rep

そして、サービス・サブプロジェクトに:

QT += remoteobjects
REPC_SOURCE += servicemessenger.rep

ソースとレプリカを接続する

サービスサブプロジェクトのmain() 関数で、Qt Remote Objects ソースノードを定義します:

#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!");

この例では、メインアプリケーションのプロセスからサービスにメッセージを送信します。この例では、メイン・アプリケーションのプロセスからサービスにメッセージを送信します。サービスは同じメッセージを返信し、デバッグ・ログに表示されます。

注: 同じ.so lib ファイルを使用する場合、同じ方法を使用できます。詳しくはUse the same .so Lib Fileをご覧ください。

QAndroidBinderの使用

QAndroidBinder は、Androidで最も重要なメソッドであるBinderを実装することで、プロセス間通信を可能にする便利なクラスです。プロセス間で または オブジェクトを送信できます。QByteArray QVariant

注意: Qt for Android では、1つのプロセスで複数のサービスを実行する場合、一度に1つのサービスしか実行できないという制限があります。そのため、各サービスは個別のプロセスで実行することを推奨します。詳細はQTBUG-78009 を参照してください。

©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。