Android
Qt

QtにおけるAndroidのサービスとスレッドの作成 -Qtで作るAndroid目覚ましもどき #6-1-

Qtで作るAndroid目覚ましもどきの第六話としていよいよ目覚ましもどきの機能実装とまいります。

概要/やること

  • 端末をスリープ状態から起床させるライブラリの作成
  • 上記ライブラリを使用するサービス/スレッドの作成
  • 上記サービスと画面との連携

なおこの核心部分の作成は下記の記事群を参考にしています。英語が得意な方は私の記事より下記を見る方が良いと思います。
https://www.kdab.com/?s=qt-android-episode
また公式の説明は下記のようです。

先に、プロジェクトの構成について

後述しますが次の要件のためプロジェクト構成を変えます。この要件を受け入れない場合は適宜対応をお願いします。

  • アクティビティとサービスのライブラリ・モジュールは別にする

前回まで話してきた部分はsummer_clockというプロジェクトになりますが、サービスをice_clockというプロジェクトにして2つのプロジェクトを統括するsummer_iceプロジェクトを作ります。すなわち下記のような構成になりますね。

summer_ice
  + summer_clock(アクティビティ)
  + ice_clock(サービス)

やり方としては……たぶん、まずsummer_iceプロジェクトを空のqmakeプロジェクトとして作るとか、そういう感じになると思います。
そしてそのサブプロジェクトとしてsummer_clockプロジェクトを入れたりice_clockプロジェクトを作ります。
これ以降はice_clockプロジェクトが主役の話となります。

端末をスリープ状態から起床させるライブラリの作成

概要

当然のようですがAndroidのAPIを使う時です。端末を起こす方法はいくつかあるようですが、今回はwakelockとkeyguardを使うことにしました。(非推奨っぽいけど)
wakelockとkeyguardを使う方法はAndroidネイティブな話としてはぐぐればいくらでも出てくるのでそちらもみてください。
ただ今回のwakelockとkeyguardの使用は前回のmediaplayerの使用より複雑なため、ある程度ネイティブな操作はJavaで行い単純化してQt/c++から制御できるようにします。

コーディング

大部分はぐぐれば組めるようなコードなはずなので詳細の説明は省きます。ソースを見てください
ただポイント……かどうか正確なところはわからないですが、Androidネイティブな実装としてはコンテキストをその場その場で取得すると思いますが、Qtではメインで動くのがc++であることから、コンテキストはその都度各ライブラリ関数のインターフェースで渡すことにします。つまりは、例えば下記です。

android/src/com/summer/test/Winter.java
    // 画面起動
    public void j_wakeup(Context ctx) {
        Log.d("SummerIce", "j_wakeup - wake");

        PowerManager power = ((PowerManager) ctx.getSystemService(Context.POWER_SERVICE));
        // ……あれ? このPowerManagerいるのか?

        wakelock.acquire();
        keylock.disableKeyguard();
        Log.d("SummerIce", "j_wakeup - unlock");
    }

コンパイル/ソースファイルの格納

上記でjavaソースを作りましたが格納する場所を一応説明します。
proファイルがあるディレクトリにAndroidmManifest.xmlが入っているandroidフォルダがあると思うので、その中に
(パッケージ名を適当に決めて)パッケージ名にあったフォルダ構成でjavaファイルを格納します。
……と、ここでAndroidmManifest.xmlの説明とか一切していないことに気付いた。
正直わかりません。KDABのソースなどをベースに自分のソースを作ったので。
なので今更ですがAndroidmManifest.xmlの作成などは下記を参考にすると良いと思います。
http://www.kdab.com/qt-android-episode-3/
Qtが古いのか画面がQt5.9と違いますが……しかしプロジェクトの設定のBuidの設定のところにある「Android APKのビルド」あたりをそれとなくいじるところは同じです。
また作成したjavaソースファイルはアクティビティもしくはサービスのプロジェクトのproファイルに下記のように書いておく必要があるようです。
参考→Android APK の準備 その2

summer_clock.pro
DISTFILES += \
    android/src/com/summer/test/Winter.java

サービスとスレッドの作成

サービスの作成

ライブラリは作れたと思うので使う人の作成に入ります。下記を参考にします。
https://www.kdab.com/qt-android-create-android-service-using-qt/

Step I: Extend QtService

サービスのクラスを適当に作ります。
私のコードではSpring.javaがそれです。
このファイルのこともDISTFILESに追加しておきます。

Step II: Add the service section(s) to your AndroidManifest.xml file

サイトの例などを参考にしてxmlにserviceのことを書きます。
私の例ではサービスのクラスはSpringで、後述のようにそのモジュール名はice_clockであるため下記のようになります。

android/AndroidManifest.xml
        <service android:process=":qt" android:name=".Spring">
        <!-- android:process=":qt" is needed to force the service to run on a separate process than the Activity -->
            <!-- Application arguments -->
            <meta-data android:name="android.app.arguments" android:value="-service"/>
            <meta-data android:name="android.app.lib_name" android:value="ice_clock"/>
       <!-- あとは適当に…… -->

Step III: How to start the service ?

サービスの始め方ですが、手動で始める(on demand)場合とアプリ起動時に自動で始める(at boot time)があるらしいです。
(よくわからないので)私は手動で始めています。
そのため上で作成したクラスの中にKDABの例と同じstartMyService()を作成しました。

Step IV: Where to put your Qt Service code?

サービスのライブラリはアクティビティのモジュールと同じにするか、アクティビティとは別のモジュールにするか、のどちらかがあります。
(よくわからないので)私は別のモジュールにしています。
※ (2018/5/26) 同じモジュールにする方法を投稿しました
ここで冒頭に書いたように、アクティビティのモジュールをsummer_clock、サービスのモジュールをice_clockとしました。
サイトの例ではまずmain()はqDebug()しかしていませんが、実際の参考ページに対応する公開ソースではQCoreApplicationを使って……という感じです。

ice_clock.cpp
#include <QCoreApplication>

#ifdef __ANDROID__
#include "snow/snow.h"
Snow snow_inst(5);
#endif

int main(int argc, char *argv[])
{
    qDebug("Summer_Ice::Ice_clock waking[S]");

    QCoreApplication app(argc, argv);

#ifdef  __ANDROID__
    // wakelock初期化
    snow_inst.init_wakelocks();
#endif /*  __ANDROID__ */

    qDebug("Summer_Ice::Ice_clock waking[E]");
    return app.exec();
}

そしてサービスを起動するために適当な時(例えばアプリ起動時)に下記を実行することにしています。

sky/sky.cpp
// サービスを開始する
void Sky::service_start() {
#ifdef    __ANDROID__
    QAndroidJniObject::callStaticMethod<void>(
                "com/summer/test/Spring",
                "startMyService",
                "(Landroid/content/Context;)V",
                QtAndroid::androidActivity().object()
                );
#endif
}

スレッドの作成

一定時間でポーリングするスレッドを作成します。
これのベースは何だったか……下記に近いか?
Qtで一定時間間隔で処理を実行するサブスレッドを作る方法まとめ
こちらも参考になりそうだ。
スレッドを使った並列処理
私のソースは特にひねりもないので説明は省きます。
しかしもしかしたらこれは正しいやりかたではないのかもしれない。QThreadクラスを継承してほにゃららというやりかたですが、QThreadについてはぐぐれば日本語の有志による説明がちゃんとあります。ちゃんとしたコードを書きたい方はそれらを見た方が良いです。
そもそも今回において言えばこのスレッドを作る目的も……


長くなったのでサービス&スレッドと画面との連携については次回にします。

余談

  • まあ、多少Androidのプログラミングに詳しい方ならなんでこんなことをやっているのかと思うでしょう
  • おそらくAlermManagerを使えばいいと思い当初はそうしようとしたが、インテントだのブロードキャストだのが理解できなくてこうなったりしています
  • またイベントのハンドラはJavaじゃね?という感じでQtを動かせなくて悩んだが……しかしJavaからc++へのinvokeもあるようなのでいずれ取り組みつもりです