我正在尝试在使用Qt编写的跨平台项目中实现远程推送通知。
我发现谷歌为Android和iOS都提供了Google Cloud Messaging服务。(https://developers.google.com/cloud-messaging/)
我需要使用一些谷歌代码并为每个平台编写一些本地代码(Android使用Java,iOS使用Objective C)。
我已经阅读了Android部分并在我的项目中实现了它,它运行良好。
现在我尝试实现iOS部分(https://developers.google.com/cloud-messaging/ios/start)。
有一些问题。
首先,我无法在Qt项目中使用CocoaPods,因此必须手动链接必要的库。
这是我的Qt项目文件:
ios {
ios_google_plist.files = $$PWD/ios/GoogleService-Info.plist
QMAKE_BUNDLE_DATA += ios_google_plist
QMAKE_INFO_PLIST = $$PWD/ios/Info.plist
LIBS += \
./ios/libs/libGGLInstanceIDLib.a \
./ios/libs/libGGLCloudMessaging.a \
./ios/libs/libGGLCore.a \
./ios/libs/libGcmLib.a \
./ios/libs/libProtocolBuffers.a \
./ios/libs/libGTMSessionFetcher_core.a \
./ios/libs/libGTMSessionFetcher_full.a \
./ios/libs/libGSDK_Overload.a \
./ios/libs/libGTM_AddressBook.a \
./ios/libs/libGTM_core.a \
./ios/libs/libGTM_DebugUtils.a \
./ios/libs/libGTM_GTMURLBuilder.a \
./ios/libs/libGTM_iPhone.a \
./ios/libs/libGTM_KVO.a \
./ios/libs/libGTM_NSDictionary+URLArguments.a \
./ios/libs/libGTM_NSScannerJSON.a \
./ios/libs/libGTM_NSStringHTML.a \
./ios/libs/libGTM_NSStringXML.a \
./ios/libs/libGTM_Regex.a \
./ios/libs/libGTM_RoundedRectPath.a \
./ios/libs/libGTM_StringEncoding.a \
./ios/libs/libGTM_SystemVersion.a \
./ios/libs/libGTM_UIFont+LineHeight.a \
./ios/libs/libGTMStackTrace.a
}
iOS应用程序构建成功,但在接收令牌时崩溃了。
以下是日志:
2015-08-21 16:59:50.735 MyCustomApp[475:96862] Attempted to configure [Identity, Analytics, AdMob, SignIn, AppInvite, CloudMessaging].
2015-08-21 16:59:50.735 MyCustomApp[475:96862] Successfully configured [].
2015-08-21 16:59:50.736 MyCustomApp[475:96862] Failed to configure [].
2015-08-21 16:59:50.736 MyCustomApp[475:96862] Subspecs not present, so not configured [Identity, Analytics, AdMob, SignIn, AppInvite, CloudMessaging].
2015-08-21 16:59:50.762 MyCustomApp[475:96862] didRegisterForRemoteNotificationsWithDeviceToken begin
2015-08-21 16:59:50.767 MyCustomApp[475:96862] didRegisterForRemoteNotificationsWithDeviceToken end
2015-08-21 16:59:50.787 MyCustomApp[475:96862] -[GGLInstanceIDTokenManager fetchTokenWithAuthorizedEntity:scope:keyPair:options:handler:]: unrecognized selector sent to instance 0x170623c20
2015-08-21 16:59:50.788 MyCustomApp[475:96862] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[GGLInstanceIDTokenManager fetchTokenWithAuthorizedEntity:scope:keyPair:options:handler:]: unrecognized selector sent to instance 0x170623c20'
*** First throw call stack:
()
libc++abi.dylib: terminating with uncaught exception of type NSException
program received signal 6,thread:17a5e;qaddr:199071490;00:0000000000000000;01:0000000000000000;02:0000000000000000;03:37364e7001000000;04:fddd569401000000;05:60d2ca6f01000000;06:6e00000000000000;07:800f000000000000;08:0000000800000000;09:0000000400000000;0a:0002000000000000;0b:0000000000000000;0c:0000000000000000;0d:0000000000000000;0e:0200000000000000;0f:0000000000000000;10:4801000000000000;11:0000000000000000;12:0000000000000000;13:0600000000000000;14:1013079901000000;15:e89c059901000000;16:b0c7217001000000;17:a09ae60101000000;18:0000000000000000;19:7a54608901000000;1a:0000000000000000;1b:0000000000000000;1c:c0b7049901000000;1d:c0d1ca6f01000000;1e:28d2589501000000;1f:a0d1ca6f01000000;20:70f24e9501000000;21:00000000;metype:5;mecount:2;medata:10003;medata:6;
正如您所看到的,registrationHandler回调函数未被调用,并且在调用registrationHandler回调函数之前发生了崩溃(在google库中的某个位置)。
我使用了谷歌的示例代码并进行了一些更改,例如将AppDelegate接口重命名为QIOSApplicationDelegate(否则Objective-C函数不会被调用)。
以下是Objective-C代码:
AppDelegateGoogle.h
#include <QtCore>
void registerDeviceForNotification_iOS_CPP(void);
并且
AppDelegateGoogle.mm
#import "Google/CloudMessaging.h"
#import <UIKit/UIKit.h>
#import "AppDelegateGoogle.h"
@interface QIOSApplicationDelegate : UIResponder <UIApplicationDelegate, GGLInstanceIDDelegate>
@property(nonatomic, strong) UIWindow *window;
@property(nonatomic, readonly, strong) NSString *registrationKey;
@property(nonatomic, readonly, strong) NSString *messageKey;
@property(nonatomic, readonly, strong) NSString *gcmSenderID;
@property(nonatomic, readonly, strong) NSDictionary *registrationOptions;
@property(nonatomic, strong) void (^registrationHandler)
(NSString *registrationToken, NSError *error);
@property(nonatomic, assign) BOOL connectedToGCM;
@property(nonatomic, strong) NSString* registrationToken;
@property(nonatomic, assign) BOOL subscribedToTopic;
@end
QIOSApplicationDelegate* pApp;
NSString *const SubscriptionTopic = @"/topics/global";
void registerDeviceForNotification_iOS_CPP(void)
{
[pApp registerDeviceForNotification_iOS];
}
@implementation QIOSApplicationDelegate
// [START register_for_remote_notifications]
- (void)registerDeviceForNotification_iOS {
// [START_EXCLUDE]
_registrationKey = @"onRegistrationCompleted";
_messageKey = @"onMessageReceived";
// Configure the Google context: parses the GoogleService-Info.plist, and initializes
// the services that have entries in the file
NSError* configureError;
[[GGLContext sharedInstance] configureWithError:&configureError];
if (configureError != nil) {
NSLog(@"Error configuring the Google context: %@", configureError);
}
_gcmSenderID = [[[GGLContext sharedInstance] configuration] gcmSenderID];
// [END_EXCLUDE]
// Register for remote notifications
UIUserNotificationType allNotificationTypes =
(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
UIUserNotificationSettings *settings =
[UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
[[UIApplication sharedApplication] registerForRemoteNotifications];
// [END register_for_remote_notifications]
// [START start_gcm_service]
[[GCMService sharedInstance] startWithConfig:[GCMConfig defaultConfig]];
// [END start_gcm_service]
__weak QIOSApplicationDelegate* weakSelf = self;
// Handler for registration token request
_registrationHandler = ^(NSString *registrationToken, NSError *error){
NSLog(@"_registrationHandler called");
if (registrationToken != nil) {
NSLog(@"Registration Token: %@", registrationToken);
std::string strToken([registrationToken UTF8String]);
Device::sendRegistrationToServer(strToken);
} else {
NSLog(@"Registration to GCM failed with error: %@", error.localizedDescription);
}
};
}
// [START register_for_remote_notifications]
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
pApp = self;
return YES;
}
- (void)subscribeToTopic {
// If the app has a registration token and is connected to GCM, proceed to subscribe to the
// topic
if (_registrationToken && _connectedToGCM) {
[[GCMPubSub sharedInstance] subscribeWithToken:_registrationToken
topic:SubscriptionTopic
options:nil
handler:^(NSError *error) {
if (error) {
// Treat the "already subscribed" error more gently
if (error.code == 3001) {
NSLog(@"Already subscribed to %@",
SubscriptionTopic);
} else {
NSLog(@"Subscription failed: %@",
error.localizedDescription);
}
} else {
self.subscribedToTopic = true;
NSLog(@"Subscribed to %@", SubscriptionTopic);
}
}];
}
}
// [START connect_gcm_service]
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Connect to the GCM server to receive non-APNS notifications
[[GCMService sharedInstance] connectWithHandler:^(NSError *error) {
if (error) {
NSLog(@"Could not connect to GCM: %@", error.localizedDescription);
} else {
_connectedToGCM = true;
NSLog(@"Connected to GCM");
// [START_EXCLUDE]
[self subscribeToTopic];
// [END_EXCLUDE]
}
}];
}
// [END connect_gcm_service]
// [START disconnect_gcm_service]
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[GCMService sharedInstance] disconnect];
// [START_EXCLUDE]
_connectedToGCM = NO;
// [END_EXCLUDE]
}
// [END disconnect_gcm_service]
// [START receive_apns_token]
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// [END receive_apns_token]
NSLog(@"didRegisterForRemoteNotificationsWithDeviceToken begin");
// [START get_gcm_reg_token]
// Start the GGLInstanceID shared instance with the default config and request a registration
// token to enable reception of notifications
[[GGLInstanceID sharedInstance] startWithConfig:[GGLInstanceIDConfig defaultConfig]];
_registrationOptions = @{kGGLInstanceIDRegisterAPNSOption:deviceToken,
kGGLInstanceIDAPNSServerTypeSandboxOption:@YES};
[[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:_gcmSenderID
scope:kGGLInstanceIDScopeGCM
options:_registrationOptions
handler:_registrationHandler];
// [END get_gcm_reg_token]
NSLog(@"didRegisterForRemoteNotificationsWithDeviceToken end");
}
// [START receive_apns_token_error]
- (void)application:(UIApplication *)application
didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(@"Registration for remote notification failed with error: %@", error.localizedDescription);
// [END receive_apns_token_error]
NSDictionary *userInfo = @{@"error" :error.localizedDescription};
[[NSNotificationCenter defaultCenter] postNotificationName:_registrationKey
object:nil
userInfo:userInfo];
}
// [START ack_message_reception]
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo {
NSLog(@"Notification received: %@", userInfo);
// This works only if the app started the GCM service
[[GCMService sharedInstance] appDidReceiveMessage:userInfo];
// Handle the received message
// [START_EXCLUDE]
[[NSNotificationCenter defaultCenter] postNotificationName:_messageKey
object:nil
userInfo:userInfo];
// [END_EXCLUDE]
}
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))handler {
NSLog(@"Notification received: %@", userInfo);
// This works only if the app started the GCM service
[[GCMService sharedInstance] appDidReceiveMessage:userInfo];
// Handle the received message
// Invoke the completion handler passing the appropriate UIBackgroundFetchResult value
// [START_EXCLUDE]
[[NSNotificationCenter defaultCenter] postNotificationName:_messageKey
object:nil
userInfo:userInfo];
handler(UIBackgroundFetchResultNoData);
// [END_EXCLUDE]
}
// [END ack_message_reception]
// [START on_token_refresh]
- (void)onTokenRefresh {
// A rotation of the registration tokens is happening, so the app needs to request a new token.
NSLog(@"The GCM registration token needs to be changed.");
[[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:_gcmSenderID
scope:kGGLInstanceIDScopeGCM
options:_registrationOptions
handler:_registrationHandler];
}
// [END on_token_refresh]
@end
有人能帮忙吗?
谢谢,Evgeny
======EDIT1 BEGIN======
当我尝试使用 -ObjC
链接选项时,出现链接错误:
在以下位置有重复符号 _OBJC_METACLASS_$_QIOSApplicationDelegate: /Users/sha/build-MyCustomApp-iphoneos_clang_Qt_5_5_0_for_iOS-Release/MyCustomApp.build/Debug-iphoneos/MyCustomApp.build/Objects-normal/arm64/AppDelegateGoogle.o /Users/sha/Qt/5.5/ios/plugins/platforms/libqios_debug.a(qiosapplicationdelegate.o) 在以下位置有重复符号 _OBJC_CLASS_$_QIOSApplicationDelegate: /Users/sha/build-MyCustomApp-iphoneos_clang_Qt_5_5_0_for_iOS-Release/MyCustomApp.build/Debug-iphoneos/MyCustomApp.build/Objects-normal/arm64/AppDelegateGoogle.o /Users/sha/Qt/5.5/ios/plugins/platforms/libqios_debug.a(qiosapplicationdelegate.o) ld: 2个重复符号,架构为 arm64 clang: 错误:链接器命令失败,退出码为 1(使用 -v 查看调用)
根据我的理解,有两个应用程序代理:
- 一个是Qt框架提供的
- 第二个是谷歌Lib提供的。
因此链接器有2个重复符号。
也许我需要将这两个应用程序代理合并成一个,但我不知道如何做到这一点......
======编辑1结束======