当前位置: 首页 > news >正文

Qt 移动应用推送通知实现

推送通知是移动应用提升用户粘性的核心功能——无论是即时消息提醒、活动推送还是状态更新,都需要通过推送功能触达用户。Qt虽未直接提供跨平台推送API,但可通过集成原生服务(如Firebase Cloud Messaging、Apple Push Notification service)实现全平台推送。本文从原理到实战,详解Qt移动应用推送通知的完整实现方案,覆盖Android和iOS双平台。

一、推送通知核心原理与架构

移动应用的推送通知需要依托平台级服务,核心架构如下:

┌─────────────┐     ┌──────────────┐     ┌──────────────┐     ┌─────────────┐
│  你的服务器  │────▶│ 平台推送服务  │────▶│ 移动设备系统  │────▶│ 你的Qt应用  │
└─────────────┘     └──────────────┘     └──────────────┘     └─────────────┘(FCM/APNs等)        (系统通知托盘)

1. 关键概念

  • 设备令牌(Token):每个设备上的应用实例会生成唯一令牌,用于标识接收终端(如FCM的registration token、APNs的device token);
  • 推送服务
    • Android:依赖Firebase Cloud Messaging(FCM);
    • iOS:依赖Apple Push Notification service(APNs);
  • 通知类型
    • 显示通知:包含标题、内容、图标等,由系统托盘展示;
    • 数据通知:无UI展示,直接传递数据给应用(需应用运行)。

二、Android平台:基于FCM的推送实现

1. 环境准备

(1)配置Firebase项目
  1. 访问Firebase控制台,创建项目并添加Android应用;
  2. 下载配置文件google-services.json,复制到Qt项目的android目录下;
  3. 在Firebase控制台启用“Cloud Messaging”服务。
(2)Qt项目配置

.pro文件中添加Android权限和依赖:

QT += androidextras# 权限配置
android {ANDROID_PACKAGE_SOURCE_DIR = $$PWD/androidandroidPermissions += \android.permission.INTERNET \com.google.android.c2dm.permission.RECEIVE \android.permission.WAKE_LOCK
}

2. 核心代码实现

(1)获取设备令牌(Token)

通过JNI调用Android原生API获取FCM令牌:

// FCMHelper.h
#ifndef FCMHELPER_H
#define FCMHELPER_H#include <QObject>
#include <QAndroidJniObject>class FCMHelper : public QObject {Q_OBJECT
public:explicit FCMHelper(QObject *parent = nullptr);void getToken();signals:void tokenReceived(const QString &token); // 令牌获取成功void tokenError(const QString &error);    // 令牌获取失败private slots:void onTokenReceived(JNIEnv *env, jobject thiz, jstring token);void onTokenError(JNIEnv *env, jobject thiz, jstring error);
};#endif // FCMHELPER_H
// FCMHelper.cpp
#include "FCMHelper.h"
#include <QtAndroid>
#include <QDebug>// JNI回调函数注册
static JNINativeMethod methods[] = {{"onTokenReceived", "(Ljava/lang/String;)V", (void*)&FCMHelper::onTokenReceived},{"onTokenError", "(Ljava/lang/String;)V", (void*)&FCMHelper::onTokenError}
};jint JNI_OnLoad(JavaVM* vm, void* reserved) {JNIEnv* env;if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)return JNI_ERR;jclass cls = env->FindClass("org/qtproject/example/FCMReceiver");env->RegisterNatives(cls, methods, sizeof(methods)/sizeof(methods[0]));return JNI_VERSION_1_6;
}FCMHelper::FCMHelper(QObject *parent) : QObject(parent) {// 初始化FCM服务QAndroidJniObject activity = QtAndroid::androidActivity();QAndroidJniObject::callStaticMethod<void>("org/qtproject/example/FCMReceiver","initialize","(Landroid/app/Activity;)V",activity.object<jobject>());
}void FCMHelper::getToken() {// 调用Java方法获取令牌QAndroidJniObject::callStaticMethod<void>("org/qtproject/example/FCMReceiver","getToken");
}// 令牌获取成功的JNI回调
void FCMHelper::onTokenReceived(JNIEnv *env, jobject thiz, jstring token) {QString tokenStr = env->GetStringUTFChars(token, nullptr);qDebug() << "FCM Token:" << tokenStr;emit tokenReceived(tokenStr);
}// 令牌获取失败的JNI回调
void FCMHelper::onTokenError(JNIEnv *env, jobject thiz, jstring error) {QString errorStr = env->GetStringUTFChars(error, nullptr);qDebug() << "FCM Error:" << errorStr;emit tokenError(errorStr);
}
(2)创建Android原生接收类

android/src/org/qtproject/example/目录下创建FCMReceiver.java

package org.qtproject.example;import android.app.Activity;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;public class FCMReceiver extends FirebaseMessagingService {private static Activity m_activity;private static native void onTokenReceived(String token);private static native void onTokenError(String error);public static void initialize(Activity activity) {m_activity = activity;}public static void getToken() {FirebaseMessaging.getInstance().getToken().addOnCompleteListener(task -> {if (!task.isSuccessful()) {onTokenError("获取令牌失败: " + task.getException().getMessage());return;}String token = task.getResult();onTokenReceived(token);});}// 接收推送消息@Overridepublic void onMessageReceived(RemoteMessage remoteMessage) {Log.d("FCM", "收到推送消息");if (remoteMessage.getNotification() != null) {// 处理显示通知String title = remoteMessage.getNotification().getTitle();String body = remoteMessage.getNotification().getBody();showNotification(title, body);}if (remoteMessage.getData().size() > 0) {// 处理数据通知(可发送到Qt应用)String data = remoteMessage.getData().toString();// 调用Qt方法传递数据(需通过JNI)}}// 显示系统通知private void showNotification(String title, String body) {// Android通知栏显示逻辑(需创建通知渠道)// ...}
}
(3)在Qt应用中使用
// 主窗口中初始化FCM
FCMHelper *fcmHelper = new FCMHelper(this);
connect(fcmHelper, &FCMHelper::tokenReceived, this, [this](const QString &token) {// 将令牌发送到你的服务器sendTokenToServer(token);
});
fcmHelper->getToken();

三、iOS平台:基于APNs的推送实现

1. 环境准备

(1)配置Apple开发者账号
  1. 在Apple开发者中心创建推送证书(APNs SSL Certificate);
  2. 在Xcode中配置项目:开启“Push Notifications”能力,导入推送证书。
(2)Qt项目配置

.pro文件中添加iOS配置:

ios {QMAKE_INFO_PLIST = Info.plistiosDeploymentTarget = 12.0
}

Info.plist中添加推送权限描述:

<key>NSRemoteNotificationUsageDescription</key>
<string>需要推送通知权限以接收消息提醒</string>

2. 核心代码实现

(1)请求推送权限并获取设备令牌

通过Objective-C++调用iOS原生API:

// APNsHelper.h
#ifndef APNHELPER_H
#define APNHELPER_H#include <QObject>
#include <QString>#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <UserNotifications/UserNotifications.h>
#endifclass APNsHelper : public QObject {Q_OBJECT
public:explicit APNsHelper(QObject *parent = nullptr);void requestPermission();signals:void tokenReceived(const QString &token);void permissionDenied();private:
#ifdef __OBJC__void registerForRemoteNotifications();
#endif
};#endif // APNHELPER_H
// APNsHelper.mm(注意文件后缀为.mm以支持Objective-C++)
#include "APNsHelper.h"
#include <QDebug>#ifdef __OBJC__
// 通知代理类
@interface QtAPNsDelegate : NSObject <UNUserNotificationCenterDelegate>
@property (nonatomic, assign) APNsHelper *helper;
@end@implementation QtAPNsDelegate
- (void)userNotificationCenter:(UNUserNotificationCenter *)centerwillPresentNotification:(UNNotification *)notificationwithCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {// 应用前台时显示通知completionHandler(UNNotificationPresentationOptionBanner | UNNotificationPresentationOptionSound);
}- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)responsewithCompletionHandler:(void(^)(void))completionHandler {// 处理通知点击事件completionHandler();
}
@end
#endifAPNsHelper::APNsHelper(QObject *parent) : QObject(parent) {
#ifdef __OBJC__// 设置通知代理QtAPNsDelegate *delegate = [[QtAPNsDelegate alloc] init];delegate.helper = this;UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];center.delegate = delegate;
#endif
}void APNsHelper::requestPermission() {
#ifdef __OBJC__UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert |UNAuthorizationOptionSound |UNAuthorizationOptionBadge)completionHandler:^(BOOL granted, NSError *error) {if (granted) {[self registerForRemoteNotifications];} else {emit permissionDenied();}}];
#endif
}#ifdef __OBJC__
void APNsHelper::registerForRemoteNotifications() {dispatch_async(dispatch_get_main_queue(), ^{[[UIApplication sharedApplication] registerForRemoteNotifications];});
}
#endif// 重写UIApplicationDelegate的方法获取令牌(需在main.m中实现)
#ifdef __OBJC__
extern "C" void applicationDidRegisterForRemoteNotificationsWithDeviceToken(UIApplication *application, NSData *deviceToken) {// 转换token为字符串const unsigned char *dataBuffer = (const unsigned char *)[deviceToken bytes];NSMutableString *tokenString = [NSMutableString stringWithCapacity:[deviceToken length] * 2];for (int i = 0; i < [deviceToken length]; ++i) {[tokenString appendFormat:@"%02.2hhx", dataBuffer[i]];}// 发送令牌到Qt信号APNsHelper *helper = ...; // 获取APNsHelper实例emit helper->tokenReceived([tokenString UTF8String]);
}
#endif
(2)在Qt应用中使用
// 初始化APNs
#ifdef Q_OS_IOS
APNsHelper *apnsHelper = new APNsHelper(this);
connect(apnsHelper, &APNsHelper::tokenReceived, this, [this](const QString &token) {sendTokenToServer(token); // 发送令牌到服务器
});
apnsHelper->requestPermission();
#endif

四、跨平台封装与统一接口

为简化双平台开发,可封装统一的推送管理类:

// PushManager.h
#ifndef PUSHMANAGER_H
#define PUSHMANAGER_H#include <QObject>
#include <QString>class PushManager : public QObject {Q_OBJECT
public:static PushManager *instance();void initialize(); // 初始化推送服务void sendTokenToServer(const QString &token); // 上传令牌到服务器signals:void notificationReceived(const QString &title, const QString &content); // 收到通知void tokenReady(const QString &token); // 令牌就绪private:PushManager(QObject *parent = nullptr);
};#endif // PUSHMANAGER_H
// PushManager.cpp
#include "PushManager.h"
#ifdef Q_OS_ANDROID
#include "FCMHelper.h"
#endif
#ifdef Q_OS_IOS
#include "APNsHelper.h"
#endif
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>PushManager *PushManager::instance() {static PushManager instance;return &instance;
}PushManager::PushManager(QObject *parent) : QObject(parent) {}void PushManager::initialize() {
#ifdef Q_OS_ANDROIDFCMHelper *fcm = new FCMHelper(this);connect(fcm, &FCMHelper::tokenReceived, this, &PushManager::tokenReady);fcm->getToken();
#endif
#ifdef Q_OS_IOSAPNsHelper *apns = new APNsHelper(this);connect(apns, &APNsHelper::tokenReceived, this, &PushManager::tokenReady);apns->requestPermission();
#endif
}void PushManager::sendTokenToServer(const QString &token) {// 发送POST请求到你的服务器,注册令牌QNetworkAccessManager *manager = new QNetworkAccessManager(this);QNetworkRequest request(QUrl("https://你的服务器地址/register_token"));request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");QByteArray data = QString("{\"token\":\"%1\"}").arg(token).toUtf8();QNetworkReply *reply = manager->post(request, data);connect(reply, &QNetworkReply::finished, [reply]() {if (reply->error() == QNetworkReply::NoError) {qDebug() << "令牌注册成功";} else {qDebug() << "令牌注册失败:" << reply->errorString();}reply->deleteLater();});
}

main.cpp中初始化:

#include "PushManager.h"int main(int argc, char *argv[]) {QGuiApplication app(argc, argv);// 初始化推送服务PushManager::instance()->initialize();// ... 其他初始化代码 ...return app.exec();
}

五、通知点击处理与应用跳转

当用户点击通知时,需打开应用并跳转到对应页面,实现方式如下:

1. Android处理

FCMReceiver.java中重写通知点击逻辑:

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {// ... 其他代码 ...Intent intent = new Intent(this, org.qtproject.qt5.android.bindings.QtActivity.class);intent.putExtra("page", "detail"); // 传递页面参数intent.putExtra("id", "123");      // 传递数据PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);// 构建通知时关联PendingIntentNotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channel_id").setContentIntent(pendingIntent);
}

在Qt中获取参数:

// 从Android intent中获取参数
#ifdef Q_OS_ANDROID
QAndroidJniObject activity = QtAndroid::androidActivity();
QAndroidJniObject intent = activity.callObjectMethod("getIntent", "()Landroid/content/Intent;");
QAndroidJniObject page = intent.callObjectMethod("getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;", QAndroidJniObject::fromString("page").object<jstring>());
if (page.isValid()) {QString pageStr = page.toString();qDebug() << "跳转页面:" << pageStr;// 执行页面跳转逻辑
}
#endif

2. iOS处理

QtAPNsDelegate中处理点击事件:

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)responsewithCompletionHandler:(void(^)(void))completionHandler {NSDictionary *userInfo = response.notification.request.content.userInfo;NSString *page = userInfo[@"page"];// 传递参数到Qt(可通过全局变量或信号)completionHandler();
}

六、最佳实践与注意事项

1. 权限与用户体验

  • 权限申请时机:在用户完成关键操作(如登录)后再请求推送权限,提高授权率;
  • 频率控制:避免频繁推送,提供“设置→通知”开关允许用户关闭;
  • 内容相关性:推送内容与用户行为相关(如购物应用推送降价提醒)。

2. 技术优化

  • 令牌刷新处理:设备令牌可能会变化(如应用重装、系统更新),需定期重新获取并同步到服务器;
  • 通知渠道:Android 8.0+必须创建通知渠道,否则通知无法显示:
    // Android创建通知渠道
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {NotificationChannel channel = new NotificationChannel("default", "默认通知", NotificationManager.IMPORTANCE_DEFAULT);NotificationManager manager = getSystemService(NotificationManager.class);manager.createNotificationChannel(channel);
    }
    
  • 后台数据处理:数据通知(无UI)需在Service(Android)或AppDelegate(iOS)中处理,避免阻塞UI。

3. 测试工具

  • FCM测试:使用Firebase控制台的“发送测试消息”功能;
  • APNs测试:使用curl命令发送测试通知:
    curl -v -d '{"aps":{"alert":"测试通知","sound":"default"}}' \
    -H "apns-topic: 你的应用Bundle ID" \
    -H "apns-push-type: alert" \
    --http2 \
    --cert 你的推送证书.pem:证书密码 \
    https://api.sandbox.push.apple.com/3/device/设备令牌
    

七、总结

Qt移动应用的推送通知实现需针对Android和iOS平台分别集成FCM和APNs服务,核心步骤包括:

  1. 配置平台推送服务并获取设备令牌;
  2. 将令牌同步到你的服务器,用于定向推送;
  3. 处理接收的通知并展示到系统托盘;
  4. 实现通知点击跳转逻辑。

通过封装跨平台接口,可显著降低双平台开发的复杂度。实际开发中需重点关注令牌刷新、权限管理和用户体验,避免过度推送导致用户反感。

掌握这套方案后,你的Qt移动应用将具备完善的推送能力,有效提升用户活跃度和留存率。

http://www.dtcms.com/a/305156.html

相关文章:

  • 多线程--关于锁的一些知识考点
  • 系统优化与性能调教
  • C++基础语法与面向对象特性
  • Blender入门笔记——建模篇(二)
  • 电商平台商品模块数据库设计
  • WEPollSelectorImpl
  • AI工作流赋能,业务的超级加速器
  • mybatis-plus代码生成器
  • 主数据管理系统能代替数据中台吗?
  • ESP32学习-1.第一个程序helloworld
  • OTA | xmodem ymodem文件传输协议收发的C语言实现
  • FlowLong工作流
  • OI 杂讲
  • ASDIP Concrete(混凝土结构设计软件) v6.0.0.2 免费版
  • 光环云 × 零一万物在上海WAIC联合发布“法律智算综合云服务”,以专业Agent助力法律普惠发展
  • debug redis里面的lua脚本
  • JSON在java中的使用
  • c++之链表
  • 技术干货 | 矢网DTF测量技术:透视线缆、天线与波导内部缺陷的“射频X光”(二)
  • 人工智能赋能社会治理:深度解析与未来展望
  • 移位运算以及定点数的加减法操作
  • 深入解析 Spring SpEL:SpelExpressionParser 的使用与实践
  • Python游戏开发:Pygame全面指南与实战
  • JAVA存储原生json字符串到redis,去除@class,实现原生命令操作教程
  • 从传统到智能:Midscene.js 如何用 AI 颠覆自动化测试!
  • 【Lua】题目小练4
  • 深入解析RocksDB的MVCC和LSM Tree level
  • 基于springboot/java/VUE的旅游管理系统/旅游网站的设计与实现
  • USB Type-C PD协议一文通
  • mangoDB面试题及详细答案 117道(026-050)