Android
Qt

Qtにおけるアクティビティとサービスの通信 -Qtで作るAndroid目覚ましもどき #6-2

前回の続きにして目覚ましもどき編の最後です。

概要/やりたいこと

アクティビティからサービスに情報を送ります。
情報とは、ここでは目覚ましの時間です。アクティビティ=画面で設定した時間をサービスに教えて規定時間に端末の画面を起こしてもらうことにします。
あとついでに、(バグなどで)サービスが停止していた場合に気付けるようにもしておきます。
前回に引き続きKDABの記事を参考にします。
"Use QtRemoteObject for communication"からです。
Qtの公式リファレンスのOverview Qt Remote Objectsも参考になるかと思います。
また「Qt Remote Object入門」を見つけました。わかりやすそうです。

内容/やること

QtRemoteObjectsライブラリの取得

KDABの記事における下記

Get QtRemoteObjects

これについてはQt5.9では不要です。はじめから組み込まれているようです。

repファイルの作成

  • create .rep file(s)

Qt公式の記事の順番でこっちから。
まあ、ありていに言って私はKDABのソースをそのままにしてあります。

proファイルの編集

Use QtRemoteObjects

  • add QtRemoteObjects to your .pro files
  • add .rep file(s) to the server .pro file
  • add .rep file(s) to the client .pro file

"QT += remoteobjects"をアクティビティとサービスの両方のproファイルに書きます。
そして私のアプリではアクティビティ(summer_ice)からサービス(ice_clock)にまず送るので、アクティビティが"クライアント"、サービスが"サーバー"となります。クライアントとサーバーという言い方はなんとなくわかりやすいですが、公式にはクライアントは"レプリカ"、サーバーは"ソース"、と呼ばれます。公式リファレンスの冒頭に何か書いてありますが私の脳ではとても理解できません。
なのでそれは置いておいて、下記に例(私のプログラム)を示します。

summer_clock.pro
    QT += remoteobjects
    REPC_REPLICA += pingpong.rep
ice_clock.pro
    QT += remoteobjects
    REPC_SOURCE += pingpong.rep

サーバー/ソースの処理実装

– QtRemoteObjects source(server) side implementation

サービス側のc++の実装からですね。
これも私のコードでもKDABのコードをそのままのような形で書いています。

ice_clock.cpp
#include "rep_pingpong_source.h"

class PingPong : public PingPongSource {
public slots:
    // PingPongSource interface
    void ping(const QString &msg) override {
        //:
        QString qstr_piyo = "piyo";
        //:
        emit pong(qstr_piyo);
    }
};

int main(int argc, char *argv[])
{
    //:
    // ping受信開始
    QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:replica")));
    PingPong pingPongServer;
    srcNode.enableRemoting(&pingPongServer);
    //:
}

はじめてプログラムを見るとピンと来ないですが、rep_pingpong_sourcePingPongSourceクラスは最初につくったrepファイルをREPC_SOURCEとして取り込んだプロジェクトに対して「モック」で生成されます。
PingPongSourceは自動で作成されるクラスですが通信を受けたときにさせたいことがあるので、これを継承したクラスでslotをoverrideするようです。
そしてmain()などでしかるべきタイミングにenableRemotingすることで"ソース"のオブジェクトを有効にします。

クライアント/レプリカの処理実装

– QtRemoteObjects replica(client) side implementation

公式リファレンスの書き方で言うと下記を行います。

  1. Create the remote node and connect it with the source host node.
  2. Acquire a replica of the remote source object.

私のプログラムにおいてはmain()を短くするためにSkyクラスの中にこれらの制御を入れてあります。

sky/sky.h
#ifdef __ANDROID__
    Q_INVOKABLE QSharedPointer<PingPongReplica> rep;
    Q_INVOKABLE QRemoteObjectNode               repNode;
#endif /* __ANDROID__ */
sky/sky.cpp
void Sky::service_start() {
#ifdef    __ANDROID__
    //:
    bool ret = repNode.connectToNode(QUrl(QStringLiteral("local:replica")));
    qDebug() << "ret = " << ret << endl;
    rep.reset(repNode.acquire<PingPongReplica>());
    bool res = rep->waitForSource(3000); //ms
    Q_ASSERT(res);
    //:
#endif /* __ANDROID__ */
    //:
}

KDABのコードをそのままだが、rep.reset(repNode.acquire<PingPongReplica>());の部分だけ違う。
これはrepQSharedPointer<PingPongReplica>だからで、QSharedPointerとかは普段c++をc言語としか使ってない無能な私には到底理解できないものだが、とにかくshared_ptrであるためポインタをセットするにはresetが必要であるよう。
またconnectToNode()を使う部分は公式リファレンスではconnect()になっている。QRemoteObjectNodeクラスのリファレンスを見てもconnect()メソッドについては書かれていなさそうだが……つまりは謎です。

通信してみる

今回のSLOTであるpingを直接使います。公式リファレンスやQt Remote Object入門では直接は使っていないようですが、まあやっていることは同じでしょう。

sky/sky.cpp
void Sky::ping2server(const QString &str) const
{
    qDebug() << "[TRC]::ping2server <- " << str;
#ifdef __ANDROID__
    //:
        rep->ping(str);
    //:
#endif /* __ANDROID__ */
}

私のプログラムでは画面で設定した時間を文字列としてソースノードであるサービスに送るので、ping2server()は文字列を引数にしています。(ここら辺はたいした話でもないので説明は省きます。ping2server()をキーワードにソースを解析してください。)

通信の応答の受信

ここで私のプログラムでやりたいことは、時間の設定がちゃんとサービスで行われたかのチェックを画面でできるようにすることです。
画面の操作に関してc++側でやるよりQML側でやる方が簡単なので応答はQMLで受けます。まずは下記です。

main.qml
    Connections {
        target: ping_pong
        onPong: {
            console.log("SummerIce::main.onPong");
            if (pingStatus == def_Send) {
                pingStatus = def_Recv;
            }
        }
    }

Connectionオブジェクトのtargetは画面起動時に設定しておきます。

summer_clock.cpp
    engine.rootContext()->setContextProperty("ping_pong", sky_inst.rep.data());

sky_inst.rep.data()の意味としては、sky_inst.repがQtのshared_ptrであるため直接ポインタを参照することはできず、QSharedPointerのdata()メソッドを使うことで参照する、ということだと思われます。
そしてsky_inst.repの大元はQRemoteObjectNodeで、このようにしてノードをターゲットにしてコネクションすることでpongイベントの発生を取得できるようになる、ということだと思う……。



まあ、こんな感じで目覚ましもどきが完成したはず。要するに端末の画面はなんとか起動させられるけどアクティビティを呼び起こす手段がまだわからなかったので現状はアプリの画面を起動させたまま端末をスリープさせないと目覚ましとして機能しない代物になっております。
Qtで作ったアクティビティを呼び起こす方法を見つけ出すことを皆様が期待してくれると思いつつ、しばしのお別れです。
ありがとうございました。