我正在尝试在我用 Qt 编写的跨平台项目中实现远程推送通知。
我发现 google 为 Android 和 iOS 提供了 Google Cloud Messaging。 ( https://developers.google.com/cloud-messaging/ ) 我需要使用一些谷歌代码并为每个平台编写一些 native 代码(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 应用程序构建成功,但在接收 token 时崩溃了。
这是日志:
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 回调并在 google lib 的某处发生崩溃(在调用 registrationHandler 回调之前)
我采用了谷歌示例代码并做了一些更改,例如我将 AppDelegate 接口(interface)重命名为 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
有人可以帮忙吗?
谢谢,叶夫根
======EDIT1 开始======
当我尝试使用 -ObjC 链接器选项时,出现链接器错误:
duplicate symbol _OBJC_METACLASS_$_QIOSApplicationDelegate in: /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) duplicate symbol _OBJC_CLASS_$_QIOSApplicationDelegate in: /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 duplicate symbols for architecture arm64 clang: error: linker command failed with exit code 1 (use -v to see invocation)
据我了解,有两个Application Delegate:
所以链接器有 2 个重复的符号。
可能我需要将这两个 Application Delegate 合并为一个,但我不知道该怎么做...
======EDIT1 结束======
最佳答案
该方法在您的应用程序中似乎未链接的类别中定义。您应该将 -ObjC 链接器标志添加到 XCode 选项,这将强制它将静态库中的类别添加到您的应用程序。
这里有更多信息 https://developer.apple.com/library/mac/qa/qa1490/_index.html
正如上面的链接所说
This flag causes the linker to load every object file in the library that defines an Objective-C class or category. While this option will typically result in a larger executable (due to additional object code loaded into the application), it will allow the successful creation of effective Objective-C static libraries that contain categories on existing classes.
关于ios - QT iOS 谷歌云消息崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32143488/
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少
我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c
这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下
我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的
我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=
我正在使用Ruby,我正在与一个网络端点通信,该端点在发送消息本身之前需要格式化“header”。header中的第一个字段必须是消息长度,它被定义为网络字节顺序中的2二进制字节消息长度。比如我的消息长度是1024。如何将1024表示为二进制双字节? 最佳答案 Ruby(以及Perl和Python等)中字节整理的标准工具是pack和unpack。ruby的packisinArray.您的长度应该是两个字节长,并且按网络字节顺序排列,这听起来像是n格式说明符的工作:n|Integer|16-bitunsigned,network(bi
1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里
如果我在模型中设置验证消息validates:name,:presence=>{:message=>'Thenamecantbeblank.'}我如何让该消息显示在闪光警报中,这是我迄今为止尝试过的方法defcreate@message=Message.new(params[:message])if@message.valid?ContactMailer.send_mail(@message).deliverredirect_to(root_path,:notice=>"Thanksforyourmessage,Iwillbeintouchsoon")elseflash[:error]
RSpec似乎按顺序匹配方法接收的消息。我不确定如何使以下代码工作:allow(a).toreceive(:f)expect(a).toreceive(:f).with(2)a.f(1)a.f(2)a.f(3)我问的原因是a.f的一些调用是由我的代码的上层控制的,所以我不能对这些方法调用添加期望。 最佳答案 RSpecspy是测试这种情况的一种方式。要监视一个方法,用allowstub,除了方法名称之外没有任何约束,调用该方法,然后expect确切的方法调用。例如:allow(a).toreceive(:f)a.f(2)a.f(1)