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

[前回](https://qiita.com/hakua-doublemoon/items/a5af13c528e132ad63c3)の続きにして目覚ましもどき編の最後です。

# 概要/やりたいこと
アクティビティからサービスに情報を送ります。
情報とは、ここでは目覚ましの時間です。アクティビティ＝画面で設定した時間をサービスに教えて規定時間に端末の画面を起こしてもらうことにします。
あとついでに、（バグなどで）サービスが停止していた場合に気付けるようにもしておきます。
前回に引き続き[KDAB](https://www.kdab.com/qt-android-create-android-service-using-qt/)の記事を参考にします。
"Use QtRemoteObject for communication"からです。
Qtの公式リファレンスの[Overview Qt Remote Objects](http://doc.qt.io/Qt-5/qtremoteobjects-gettingstarted.html)も参考になるかと思います。
また「[Qt Remote Object入門](https://qiita.com/hermit4/items/1947ec22cfa28ee17b17)」を見つけました。わかりやすそうです。

# 内容/やること
## 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_source`や`PingPongSource`クラスは最初につくったrepファイルをREPC_SOURCEとして取り込んだプロジェクトに対して「モック」で生成されます。
`PingPongSource`は自動で作成されるクラスですが通信を受けたときにさせたいことがあるので、これを継承したクラスでslotをoverrideするようです。
そしてmain()などでしかるべきタイミングにenableRemotingすることで"ソース"のオブジェクトを有効にします。

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

> – QtRemoteObjects replica(client) side implementation

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

> 2. Create the remote node and connect it with the source host node.
> 3. 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>());`の部分だけ違う。
これは`rep`が`QSharedPointer<PingPongReplica>`だからで、QSharedPointerとかは普段c++をc言語としか使ってない無能な私には到底理解できないものだが、とにかくshared_ptrであるためポインタをセットするにはresetが必要であるよう。
また`connectToNode()`を使う部分は公式リファレンスでは`connect()`になっている。QRemoteObjectNodeクラスのリファレンスを見てもconnect()メソッドについては書かれていなさそうだが……つまりは謎です。

## 通信してみる
今回のSLOTである`ping`を直接使います。公式リファレンスや[Qt Remote Object入門](https://qiita.com/hermit4/items/1947ec22cfa28ee17b17)では直接は使っていないようですが、まあやっていることは同じでしょう。

``` 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イベントの発生を取得できるようになる、ということだと思う……。

<br>

----

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