ios Google Cloud Messaging (GCM) 未收到远程通知
Posted
技术标签:
【中文标题】ios Google Cloud Messaging (GCM) 未收到远程通知【英文标题】:ios Google Cloud Messaging (GCM) not receiving remote notifications 【发布时间】:2015-08-11 19:51:43 【问题描述】:问题: ios 没有从 GCM 接收任何远程通知,但找不到任何与为什么会出现这种情况相关的信息。 第一次实现推送通知,不知道是什么原因造成的。
情况: 我目前正在开发使用 GCM 进行推送通知的应用程序的 iOS 版本。在 android 上可以正常接收通知,但在 iOS 上似乎根本没有接收到。
当我运行应用程序时,控制台显示一切都很好,有一个令牌,连接到 GCM 并订阅了主题
app[579:45511] 注册令牌: bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1pLTQ/8t-5QNiXbYwZYEWiSFD-frQKlsV8lgI 应用程序[579:45511] 已连接到 GCM 应用[579:45511] 已订阅 /topics/global
但是它没有收到任何通知,当我拉下通知中心或拉起控制中心时,控制台中会出现以下消息
app[579:45511] 无法连接到 GCM:操作无法完成。 (com.google.gcm 错误 2001。)
除了指...
/// 缺少密钥对。 kGGLInstanceIDOperationErrorCodeMissingKeyPair = 2001,
另一方面,当我使用多任务功能将其发送到后台并将其带回来时,我又得到了这个:
应用[579:45511] 已连接到 GCM 应用[579:45511] 已订阅 /topics/global
设置: 我已经按照 GCM 说明在 iOS 中进行设置,甚至参考了 GcmExample.xcodeproj 的实现(代码完全相同)。
为“必需的后台模式”设置 info.plist ->“应用下载内容以响应推送通知”
遇到另一个关于 GCM 和 IP 未列入白名单的 *** 问题(现在找不到),但排除了这不是问题。
代码:
#import "AppDelegate.h"
@interface AppDelegate ()
@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
NSString *const SubscriptionTopic = @"/topics/global";
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
// Override point for customization after application launch.
// [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 typeof(self) weakSelf = self;
// Handler for registration token request
_registrationHandler = ^(NSString *registrationToken, NSError *error)
if (registrationToken != nil)
weakSelf.registrationToken = registrationToken;
NSLog(@"Registration Token: %@", registrationToken);
[weakSelf subscribeToTopic];
NSDictionary *userInfo = @@"registrationToken":registrationToken;
[[NSNotificationCenter defaultCenter] postNotificationName:weakSelf.registrationKey
object:nil
userInfo:userInfo];
else
NSLog(@"Registration to GCM failed with error: %@", error.localizedDescription);
NSDictionary *userInfo = @@"error":error.localizedDescription;
[[NSNotificationCenter defaultCenter] postNotificationName:weakSelf.registrationKey
object:nil
userInfo:userInfo];
;
[[NSNotificationCenter defaultCenter] postNotificationName:_messageKey
object:nil
userInfo:nil];
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);
];
- (void)applicationWillResignActive:(UIApplication *)application
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
// [START disconnect_gcm_service]
- (void)applicationDidEnterBackground:(UIApplication *)application
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
[[GCMService sharedInstance] disconnect];
// [START_EXCLUDE]
_connectedToGCM = NO;
// [END_EXCLUDE]
// [END disconnect_gcm_service]
- (void)applicationWillEnterForeground:(UIApplication *)application
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
- (void)applicationDidBecomeActive:(UIApplication *)application
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
// 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]
- (void)applicationWillTerminate:(UIApplication *)application
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// [START receive_apns_token]
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
// [END receive_apns_token]
// [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]
// [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];
- (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
更新 用于发送 GCM 消息的后端 php 代码
//------------------------------
// Payload data you want to send
// to Android device (will be
// accessible via intent extras)
//------------------------------
$msg = addslashes($_POST["msg"]);
//------------------------------
// The recipient registration IDs
// that will receive the push
// (Should be stored in your DB)
//
// Read about it here:
// http://developer.android.com/google/gcm/
//------------------------------
//------------------------------
// Call our custom GCM function
//------------------------------
sendGoogleCloudMessage( $msg );
echo "send";
//------------------------------
// Define custom GCM function
//------------------------------
function sendGoogleCloudMessage( $msg )
//------------------------------
// Replace with real GCM API
// key from Google APIs Console
//
// https://code.google.com/apis/console/
//------------------------------
$apiKey = 'abc';
//------------------------------
// Define URL to GCM endpoint
//------------------------------
$url = 'https://android.googleapis.com/gcm/send';
//------------------------------
// Set CURL request headers
// (Authentication and type)
//------------------------------
$headers = array(
'Authorization: key=' . $apiKey,
'Content-Type: application/json'
);
//------------------------------
// Initialize curl handle
//------------------------------
$ch = curl_init();
//------------------------------
// Set URL to GCM endpoint
//------------------------------
curl_setopt( $ch, CURLOPT_URL, $url );
//------------------------------
// Set request method to POST
//------------------------------
curl_setopt( $ch, CURLOPT_POST, true );
//------------------------------
// Set our custom headers
//------------------------------
curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
//------------------------------
// Get the response back as
// string instead of printing it
//------------------------------
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
//------------------------------
// Set post data as JSON
//------------------------------
$post_json_encode = '"data":"message":"' . $msg . '","to":"/topics/global"';
curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_json_encode );
//------------------------------
// Actually send the push!
//------------------------------
$result = curl_exec( $ch );
//------------------------------
// Error? Display it!
//------------------------------
if ( curl_errno( $ch ) )
echo 'GCM error: ' . curl_error( $ch );
//------------------------------
// Close curl handle
//------------------------------
curl_close( $ch );
//------------------------------
// Debug GCM response
//------------------------------
$arr_result = json_decode($result, true);
foreach ($arr_result as $name => $value)
echo "<p>".$name .": ". $value ."</p>";
【问题讨论】:
我也有同样的问题。但是,我确实在“didReceiveRemoteNotification”中的 userInfo 中收到了良好的内容,并且我可以对其进行 NSLog,但由于某种原因,如果我尝试发布通知,我会收到与您相同的错误。您是否也收到了消息的内容? 看看你如何发送消息会很有用,即你的 http 请求(或 xmpp 节)的内容,当然,删除了 API 密钥。 @Gannicus 对我来说很不幸,看起来我的应用根本没有收到任何通知。我在每个 didReceiveRemoteNotification 函数中都设置了断点来检查,但没有。我还注意到在 Google 提供的 GcmExample 中,同样的情况下也会产生 'Could not connect' 错误 2001。 @TheWonderBird 正如我在上面对 Gannicus 提到的,应用程序没有收到任何请求,所以我看不到请求进入。我确定我还能看到 http 请求的内容,但我已更新以包含 PHP GCM 文件以显示身份验证标头和 json,希望对您有所帮助。有什么建议或意见吗? @setzuiro 你得到的错误 2001 不是kGGLInstanceIDOperationErrorCodeMissingKeyPair
而是kGCMServiceErrorCodeAlreadyConnected
。后者意味着您已经连接到 GCM。为了更好地调试,我会尝试向设备令牌发送一个显示通知,即发送这个$post_json_encode = '"notification":"body":"' . $msg . '","to":"/topics/global"';
理论上你应该在你的应用程序处于前台时连接到 GCM,当你进入后台时断开连接。当您来到前台时,您可以再次重新连接。
【参考方案1】:
您得到的错误 2001 不是kGGLInstanceIDOperationErrorCodeMissingKeyPair
,而是kGCMServiceErrorCodeAlreadyConnected
。后者意味着您已经连接到 GCM。为了更好地调试,我会尝试向设备令牌发送显示通知,即发送此
$post_json_encode = '"notification":"body":"' . $msg . '","to":"/topics/global"';
理论上,您应该在应用处于前台时连接到 GCM,而在您进入后台时断开连接。当您来到前台时,您可以再次重新连接。
data payload
和 notification payload
都适用于 iOS 和 Android。在 iOS 上,不同之处在于通知负载通过 APNS 发送,而数据负载通过 GCM 自己的连接发送,该连接仅在应用程序处于前台时才存在。在 Android 通知负载中是最近添加的新显示通知内容。
【讨论】:
这是否意味着如果只有数据有效负载,则在应用程序处于后台时无法获得通知?我希望不是因为我使用了这个 java 库 github.com/google/gcm,它似乎没有通知有效负载。 不,您仍然可以使用content_available
通过 APNS 发送消息,即使您的有效负载中没有 notification
密钥。例如,这个载荷 "content_available" : true, "data" : "key1" : "value1"
将通过 APNS 并在后台唤醒应用程序以允许它获取一些新数据。
@evanescent 我尝试在后台使用: "to":"/topics/ios-tests", "data": "m":"TEST 11112 WITH NOTIFICATION", "e":1443045599999 , "content_available":true, "priority": 10
发送推送通知,但它只适用于前台。如果我的应用程序进入后台或被杀死,它不会收到任何通知。你能告诉我我做错了什么吗?我的 iOS 应用代码与 GCM 示例中的相同
如果我使用 notification payload
,我的应用会收到通知,但我想在不显示用户通知的情况下在后台下载数据
@granan 您错误地使用了priority
参数。它的值应该是high
或low
("priority": "high"
)。 high
对应于 APNS 中的 10
,low
对应于 5
。更多参考developers.google.com/cloud-messaging/http-server-ref【参考方案2】:
我在 iOS 上遇到了同样的问题。然后我在 PushBots 网站上找到了解决方案。现在对我来说工作正常。
在 XCode 中转到 Targets > Build settings > Code Signing Identity 并确保它不是自动的,并设置为链接到应用程序 ID 的配置文件匹配证书,例如
【讨论】:
以上是关于ios Google Cloud Messaging (GCM) 未收到远程通知的主要内容,如果未能解决你的问题,请参考以下文章
Google Cloud Messaging:iOS App 在后台时不会收到警报
ios Google Cloud Messaging (GCM) 未收到远程通知
如何使用 Google Cloud Messaging 将通知推送到 iOS
Parse.com 推送通知 VS。适用于 iOS 的 Google Cloud 消息传递?