学生プロジェクト:リモートプッシュ通知

このブログは「Student project: Remote push notifications」の抄訳です。

はじめに

TheBoys
左からは Jakob Mats Emil Wirén, Bror Wetlesen Vedeld, Aksel Matashev

2025年春、ノルウェー科学技術大学ギョヴィク校の学生3名(Jakob Mats Emil Wirén、Aksel Matashev、Bror Wetlesen Vedeld)が、Qtオスロオフィスの支援のもと、学士課程プロジェクトを実施しました。このプロジェクトの目的は、ネイティブのプッシュ通知サービスをQtアプリケーションにクロスプラットフォームで統合する方法を調査することでした。
その成果として、QtPushNotificationsという実験的なライブラリが開発されました。実装は以下のリンクからご覧いただけます:https://git.qt.io/nils.petter.skalerud/qtpushnotifications

なお、執筆時点ではこの機能をQtに公式機能として統合する予定はありません。このプロジェクトは主に研究目的で行われたものです。

Thesis

エンドユーザー向けアプリケーション、特にモバイルアプリでは、ユーザーに更新情報を通知するニーズがますます高まっています。これには、テキストメッセージ、通話、天気予報、交通情報などの通知が含まれます。多くの最新プラットフォームでは、アプリケーションがバックグラウンドで動作している場合や、デバイスがスリープ状態のときに、インターネットへの接続方法に厳しい制限が設けられています。これにより、従来のネットワーク通信手法を使用してアプリケーション内でWeb上のイベントを監視することが困難になります。これらのプラットフォームが提供する解決策が、リモートプッシュ通知です。

このプロジェクトにおけるリモートプッシュ通知とは、サーバーがオペレーティングシステムによって管理される中央のポーリングループプロセスにネットワーク通知を送信する、リモートのシステムレベルのアラートを指します。プラットフォームは、アプリケーションがイベントを監視するために利用できる中央APIを提供します。このソリューションにより、オペレーティングシステムとエンドユーザーは、アプリケーションがインターネットに接続する方法をより細かく制御できるようになり、デバイスのバッテリー寿命の向上にもつながります。これらのソリューションの例としては、Apple Push Notification Services、Firebase Cloud Messaging、Windows Notification Service などがあります。

現時点では、QtフレームワークにはQtアプリケーション開発者がこの機能(リモートプッシュ通知)を活用するための機能は備わっていません。この学生プロジェクトでは、単一のクロスプラットフォームAPIのもとで、この機能をQtフレームワークに統合する方法を調査しました。QtPushNotificationsで示されている作業は、主にアプリケーションコード内に含まれるリモートプッシュ通知の受信機能に焦点を当てています。

また、ネイティブサービスと通信するためのサーバーコンポーネントのモックアップも提供しています。これは、クライアント側で動作するQtPushNotificationsライブラリと通信する QHttpServer を使用して構築されています(下図では「App Server」として示されています)。ただし、QHttpServerはパブリックネットワーク上での運用に耐えるよう設計されていないため、アプリケーション開発者はこのサーバーコンポーネントを完全に再実装する必要があります。すべてのサーバーコンポーネントはREST APIを使用しており、私たちのモックアップ実装とリポジトリ内のドキュメントは、クライアントとMicrosoftおよびAppleのネイティブプッシュ通知サービスとの通信方法の参考として活用できます。

リモートプッシュ通知には、技術的にもユーザー体験的にも多くの利点があります。特に注目すべきは以下の点です:

  • アプリが待機中、バックグラウンド動作中、あるいは一部のプラットフォームでは終了していても、サーバーからクライアントアプリに対してリモートで状態変更をトリガーできる
  • 通常はフォアグラウンドや特権状態でのみ可能なワークフローを、リモートから起動できる

QtPushNotificationsライブラリは、クライアント側アプリケーション向けに設計されています。
通知を送信するサーバーの構築は、アプリケーション開発者の責任です。

通知の配信フロー全体は、すべてのプラットフォームでほぼ共通しており、以下の図で示されています:

PushNotificationWorkflow

  1. パーミッションリクエスト(任意)
    Qtクライアントアプリケーションは、プッシュ通知を受信するための許可をオペレーティングシステムに要求します。このステップは任意であり、アプリが通知の登録を行えるかどうかを判断するためのものです。ユーザーの同意がなくても、リモートプッシュ通知の実装は可能です。

  2. 識別子のリクエスト
    アプリケーションは、ネイティブのプッシュ通知サービス(例:APNs、WNS、FCM)に接続し、通知のターゲットとなる一意のデバイスまたはアプリケーション識別子を要求します。

  3. 識別子のレスポンス
    プッシュ通知サービスは、一意の識別子(一般的にはトークンまたはURI)を返します。この識別子は、特定のデバイス/アプリケーションインスタンスを表し、以降のメッセージ送信時に使用されます。

  4. 識別子の登録
    アプリケーションはこの識別子をアプリサーバーに転送します。サーバーはそれをデータベースに保存し、後でそのデバイスに通知を送信する際に使用します。

  5. メッセージの送信
    サーバーが通知を送信したい場合、保存された識別子を使用して、対応するクラウドサービス(例:APNs/WNS/FCM)にメッセージを送信します(通常はHTTPリクエスト経由)。

  6. 通知の配信
    プッシュ通知サービスは、通知をユーザーのデバイスに配信します。アプリケーションが実行中であれば、メッセージは直接アプリに渡されます。アプリケーションが終了している場合、OSがアプリを起動する(許可されている場合)、またはプラットフォームの機能とメッセージの種類に応じてシステム通知を表示します。


現在、QtPushNotificationsライブラリは WNS(Windows Notification Services) と APNs(Apple Push Notification Services) をサポートしています。開発には Qt 6.9 を使用しました。学生プロジェクト期間中には、AndroidとFirebase Cloud Messagingを使ったプロトタイプの検証も行われましたが、夏季インターン期間の制約により、Android対応は見送られました。

システムのセットアップには、すべてのプラットフォームでクラウドサービスの認証情報やアプリケーションマニフェストなど、プラットフォーム固有の設定が必要ですが、構成自体は意図的に軽量に設計されています。

一部のインフラコンポーネントは標準では同梱されていませんが、コア機能はすでに実装済みです。
デバイスはリモート通知の登録が可能で、Qtらしいシグナルベースのインターフェースを通じて、ネイティブサービス経由でメッセージの送受信が行えます。詳細なビルド手順やセットアップガイドは、プロジェクトのドキュメントフォルダに記載されています。

APIの使用方法

サービスの登録

リモートプッシュ通知を機能させるには、アプリケーション開発者が対象プラットフォームのプッシュ通知サービスにアカウント登録する必要があります。QtPushNotificationsのリポジトリには、APNsやWNSから取得すべき情報やキーに関するドキュメントが含まれています。

アプリケーションへのAPI追加

リモートプッシュ通知の機能は、複数のプラットフォームにおいて、デプロイプロセスやアプリケーションマニフェストと密接に関連しています。そのため、QtPushNotificationsでは、アプリケーション識別子をビルド時(特にCMake構成)にアプリケーションへ渡すことが必須です。これらの識別子は、デプロイされたアプリケーションバイナリと、プッシュ通知サービスに登録されたアプリケーションを紐づける役割を果たします。

そのため、QtPushNotificationsは従来の target_link_librariesを使って直接リンクするのではなく、 qtpushnotifications_link() というCMake関数を提供しています。この関数が、識別子と必要なライブラリをアプリケーションに適用します。

qtpushnotifications_link(target
    ENABLE_APPLE
        APPLE_TEAM_ID         "my-apple-team-id"
        APPLE_BUNDLE_ID       "org.example.myapplication"
        IOS_INFO_PLIST        "path/to/ios/info.plist"
        IOS_ENTITLEMENTS      "path/to/ios/entitlements.plist"
        MAC_INFO_PLIST        "path/to/mac/info.plist"
        MAC_ENTITLEMENTS      "path/to/mac/entitlements.plist"
    ENABLE_WINDOWS
        WINDOWS_AZURE_ID      "<azure-app-identifier>"
)

最小限の使用例

C++でプロジェクトを使う方法

 CMake関数のおかげで、プッシュ通知を有効にするためのアプリケーションコードは最小限で済みます。すべての機能は、シングルトンクラスQPushNotificationReceiverを通じて呼び出されます。アプリケーションをプッシュ通知の受信対象として登録するには、以下の2つの関数を呼び出すだけです。

void QPushNotificationReceiver::registerWithService()

この関数は、アプリケーションが現在動作しているOSネイティブのプッシュ通知サービスに接続します。成功・失敗のいずれかでシグナルが発行され、成功時にはアプリケーションとユーザーのデバイスに固有の識別子が含まれます。この識別子の取り扱いは、アプリケーション開発者の責任です。生成された識別子は、アプリケーションに関連するサーバーインフラへ送信してください。

サーバーインフラはこの識別子を使って、WNSやAPNsと連携し、個々のアプリケーションにペイロード(通知メッセージ)を送信できます。このプロジェクトにおけるペイロードとは、通知本文や受信メッセージを指し、Qtでは pushNotificationReceived シグナルを通じて QString として返されます。

#include <QPushNotificationReceiver>

// Add signal connection for when push notifications are received
QObject::connect(
    QPushNotificationReceiver::instance(),
    &QPushNotificationReceiver::pushNotificationReceived,
    QApplication::instance(),
    [](const QString &payload) {
        qDebug << "Notification received with body: " << payload;
    });

// Add signal connection for when registration is complete
QObject::connect(
    QPushNotificationReceiver::instance(),
    &QPushNotificationReceiver::registrationComplete,
    QApplication::instance(),
    [](const QString &token, const QString &serviceIdentifier) {
        qDebug << "token generated: " << token 
               << " using protocol: " << serviceIdentifier;
    });

// Initialize the QPushNotificationReceiver
QPushNotificationReceiver::instance()->registerWithService();
void QPushNotificationReceiver::registerWithService(QUrl, QJsonObject, QNetworkAccessManager*)

このオーバーロード関数は、登録処理を実行し、利用可能なすべてのデータを指定された QUrl に送信します。QJsonObject を使ってカスタムデータを渡すことができ、関数はネイティブのプッシュ通知サービスから取得したすべてのトークンをそのデータに追加します。実際には、この関数は指定されたURLに対して HTTPのPOSTリクエスト を送信します。リクエストのフォーマットは、リポジトリ内でドキュメント化されています。さらに、ネイティブのプッシュ通知サービスとの通信を実装したサーバーインフラのリファレンス実装も含まれています。これにより、送信されたデータの解析方法や、APNs/WNSとの連携方法の参考として活用できます。

プッシュ通知の文脈においてQJsonObject に追加できるメタデータの例:

  • deviceModel, osName, osVersion: デバイスやOSの識別に使用。互換性やターゲティングに有効
  • appVersion: 既知のバグや挙動を持つクライアントをフィルタリングする際に役立つ
  • language, timezone: ユーザーの文脈に応じたローカライズや通知スケジューリングが可能

アプリケーション開発者は、POSTリクエストの送信に使用する QNetworkAccessManager の参照も提供できます。

また、デバイス識別子を受け取るサーバー側のハンドラの構築は、開発者の責任です。
このREST APIの仕様は、プロジェクトのドキュメントフォルダに記載されています。
さらに、リポジトリには QHttpServer をベースにしたモックアップ実装も含まれており、ネイティブのプッシュ通知サービスとの通信方法の参考として活用できます。

https://git.qt.io/nils.petter.skalerud/qtpushnotifications/-/blob/main/docs/REST-API-for-sending-and-receiving-keys.md?ref_type=heads

#include <QPushNotificationReceiver>

// Add signal connection for when push notifications are received
QObject::connect(
    QPushNotificationReceiver::instance(),
    &QPushNotificationReceiver::pushNotificationReceived,
    QApplication::instance(),    
    [](const QString &payload) {
        qDebug << "Notification received with body: " << payload; 
    });
 
// Initialize the QPushNotificationReceiver
QPushNotificationReceiver::instance()->registerWithService(
    QUrl::fromUserInput("127.0.0.1"),
    QJsonObject{},
    &nam);

QMLでプッシュ通知を使う

QMLでプッシュ通知を受信するには、QtPushNotifications モジュールをインポートし、PushNotificationReceiver シングルトンを使用します。このオブジェクトは、通知が届いたときにシグナルを発行します。QMLの Connections を使ってそのシグナルを処理できます。なお、接続処理はC++側で行う必要があります。

最小構成の使用例:

import QtQuick
import QtQuick.Controls
import QtPushNotifications

ApplicationWindow {
    id: window
    visible: true
    width: 256
    height: 512

    Connections {
        target: PushNotificationReceiver

        function onPushNotificationReceived(notificationBody) {
            console.log("Received push notification with body: " + notificationBody);
        }
    }
}

より高度な使い方については、公式ドキュメントをご覧ください:

https://git.qt.io/nils.petter.skalerud/qtpushnotifications/-/blob/main/docs/QML-PushNotificationReceiver.md?ref_type=heads

使用例

リポジトリには、2つの使用例が含まれています。どちらも クライアント(エンドユーザー) と サーバーコンポーネント を持ち、両方とも C++ と Qt で記述されています。

リポジトリ内にある最小構成の例では、プッシュ通知を設定し、ユーザーにシンプルな通知を送信するために必要な最小限のコードを紹介しています。サーバーアプリケーション(管理者用アプリ)は QWidgets アプリケーションで、アプリを実行している登録済みデバイスを表示し、管理者がすべてのデバイスに通知を送信できます。クライアントアプリケーションは QML アプリケーションで、通知を受信するとバックグラウンドからポップアップ表示されます。

さらに、より複雑な使用例として、プッシュ通知を使ってメッセージを送受信するチャットアプリケーションを開発しました。アプリが非アクティブな状態でもメッセージを受信できるよう設計されています。このチャット例は、プッシュ通知の**実用的な活用方法を示すPoC(概念実証)**として作成されました。サーバーと複数のクライアントのセットアップがかなり複雑なため、構築を推奨するのは、最も粘り強く勇敢な開発者のみです。

 


Blog Topics:

Comments