谷歌云消息推送(Google Cloud Messaging):iOS应用在后台时无法接收通知提示

6
我按照这个教程 https://developers.google.com/cloud-messaging/ios/client 实现了在我的 iOS 应用程序中使用 GCM。我的应用服务器是使用 Java 编写的 Google App Engine,我使用了 gcm-server.jar 库 https://github.com/google/gcm。我认为我的证书没问题,我可以注册、获取令牌,甚至接收由我的应用服务器发送的消息内容。但是,当应用程序处于后台时,我不会收到任何通知提醒,只有当我点击应用程序图标重新启动它时才会收到通知。
我以为这是因为我只实现了 didReceiveRemoteNotification: 而没有实现 didReceiveRemoteNotification:fetchCompletionHandler: ,所以我改为使用后者进行实现,但是当应用程序在后台时,我仍然无法收到通知,更糟糕的是,应用程序会崩溃,提示类似“未识别的选择器发送到实例 didReceiveRemoteNotification:”之类的错误,好像 userInfo 中出现了一些问题。我已经在 xCode 中允许了后台模式,代码如下:
AppDelegate ()

@property (nonatomic, strong) NSDictionary *registrationOptions;
@property (nonatomic, strong) GGLInstanceIDTokenHandler registrationHandler;

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

//-- Set Notification
[[GCMService sharedInstance] startWithConfig:[GCMConfig defaultConfig]];
if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)])
{
    NSLog(@"Case iOS8");
    // iOS 8 Notifications
    [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];

    [application registerForRemoteNotifications];
}
else
{
    NSLog(@"Case iOS7");
    // iOS < 8 Notifications
    [application registerForRemoteNotificationTypes:
     (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)];
}

self.registrationHandler = ^(NSString *registrationToken, NSError *error){
    if (registrationToken != nil) {

        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        [defaults setObject:registrationToken forKey:TOKENGCM];
        NSLog(@"Registration Token: %@", registrationToken);
        //some code
    } else {
        NSLog(@"Registration to GCM failed with error: %@", error.localizedDescription);
    }
};
return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
[[GCMService sharedInstance] disconnect];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
}

- (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 {
        NSLog(@"Connected to GCM");
        // ...
    }
}];
}

- (void)applicationWillTerminate:(UIApplication *)application {
}


- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// Start the GGLInstanceID shared instance with the default config and request a registration
// token to enable reception of notifications
[[GGLInstanceID sharedInstance] startWithConfig:[GGLInstanceIDConfig defaultConfig]];
self.registrationOptions = @{kGGLInstanceIDRegisterAPNSOption:deviceToken,
                         kGGLInstanceIDAPNSServerTypeSandboxOption:@NO};
[[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:SENDER_ID
                                                    scope:kGGLInstanceIDScopeGCM
                                                  options:self.registrationOptions
                                                  handler:self.registrationHandler];
}

- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
NSLog(@"Error in registration. Error: %@", err);
}

- (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:SENDER_ID
                                                    scope:kGGLInstanceIDScopeGCM
                                                  options:self.registrationOptions
                                                  handler:self.registrationHandler];
}


- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo {
NSLog(@"Notification received: %@", userInfo);//This does print the content of my message in the console if the app is in foreground
UIApplicationState state = [application applicationState];
if (state == UIApplicationStateActive) {
    NSString *cancelTitle = @"Close";
    NSString *showTitle = @"Show";
    NSString *message = [[userInfo valueForKey:@"aps"] valueForKey:@"alert"];
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Some title"
                                                        message:message
                                                       delegate:self
                                              cancelButtonTitle:cancelTitle
                                              otherButtonTitles:showTitle, nil];
    [alertView show];
}
else{
    NSLog(@"Notification received while inactive");
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber: 99];
    UIAlertView *BOOM = [[UIAlertView alloc] initWithTitle:@"BOOM"
                                                   message:@"app was INACTIVE"
                                                  delegate:self
                                         cancelButtonTitle:@"a-ha!"
                                         otherButtonTitles:nil];
    [BOOM show];
    NSLog(@"App was NOT ACTIVE");
   [[NSNotificationCenter defaultCenter] postNotificationName:@"Notification!"
                                                        object:nil
                                                      userInfo:userInfo];
}
// This works only if the app started the GCM service
[[GCMService sharedInstance] appDidReceiveMessage:userInfo];
}

//Implement that causes unrecognized selector crash 
- (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:@"notif"
                                                    object:nil
                                                  userInfo:userInfo];
handler(UIBackgroundFetchResultNoData);
// [END_EXCLUDE]
}

@end

有人能够解决我在不在前台时为什么无法收到通知的问题吗?

编辑:用于发送GCM消息的服务器端Java代码:

public static MulticastResult sendViaGCM(String tag, String message, List<String> deviceIdsList) throws IOException {
    Sender sender = new Sender(Constantes.API_KEY);
    // This message object is a Google Cloud Messaging object
    Message msg = new Message.Builder().addData("tag",tag).addData("message", message).build();
    MulticastResult result = sender.send(msg, deviceIdsList, 5);
    return result;
}
编辑2: POST请求的屏幕截图 http://image.noelshack.com/fichiers/2015/34/1440193492-gcm1.png http://image.noelshack.com/fichiers/2015/34/1440193502-gcm2.png 编辑3: 我现在从我的应用程序服务器发送的请求:
public static void sendGCMMessage(String tag, String message, List<String> deviceIdsList) {
    String request = "https://gcm-http.googleapis.com/gcm/send";
    try{
        URL url = new URL(request);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setDoOutput(true);
        //conn.setInstanceFollowRedirects(false);
        conn.setRequestMethod("POST");
        //Les deux headers obligatoires:
        conn.setRequestProperty("Content-Type", "application/json");
        conn.setRequestProperty("Authorization", "key=" + API_KEY);
        //Construction du JSON:
        JSONObject fullJSON = new JSONObject();
        JSONObject data=new JSONObject();
        JSONObject notification=new JSONObject();
        data.put("tag", tag);
        data.put("message", message);
        notification.put("sound", "default");
        notification.put("badge", "1");
        notification.put("title", "default");
        notification.put("body", message);
        fullJSON.put("registration_ids", deviceIdsList);

        fullJSON.put("notification", notification);
        fullJSON.put("content_available", "true");
        fullJSON.put("data", data);

        //Phase finale:
        OutputStreamWriter wr= new OutputStreamWriter(conn.getOutputStream());
        wr.write(fullJSON.toString());
        wr.flush();
        wr.close();//pas obligatoire
        //conn.setUseCaches(false);
    }
    catch(Exception e){
        e.printStackTrace();
    }
2个回答

12

根据GCM文档,您可以将content_available设置为true

(在iOS上,使用此字段表示APNS负载中的内容可用性。当发送通知或消息并将其设置为true时,将唤醒非活动客户端应用。在Android上,默认情况下,数据消息会唤醒应用程序。在Chrome上,当前不支持。)

content_available对应于苹果的content-available,您可以在此苹果推送通知服务文档中找到。

此外,您应该使用通知有效负载向您的iOS应用程序发送消息,以便在您的应用程序处于后台时显示横幅。

以下是示例HTTP请求:

https://gcm-http.googleapis.com/gcm/send
Content-Type:application/json
Authorization:key=API_KEY
{
   "to" : "REGISTRATION_TOKEN",
   "notification" : {
     "sound" : "default",
     "badge" : "1",
     "title" : "default",
     "body"  : "Test",
   },
   "content_available" : true,
}

Java库仅仅是一个示例,你可以添加其他字段。例如,在Message.java类中,你可以添加两个私有变量,一个是private final Boolean contentAvailable,另一个是private final Map<String, String> notification

你可以在终端中尝试使用HTTP请求,方法是执行curl -i -H "Content-Type:application/json" -H "Authorization:key=API_KEY" -X POST -d '{"to":"REGISTRATION_TOKEN", "notificaiton":{"sound":"default", "badge":"1", "title": "default", "body":"test",},"content_available":true}' https://android.googleapis.com/gcm/send,或者在Postman中尝试。

已编辑:

如果你的应用程序被终止,但你希望推送通知在你的设备上显示,你可以在HTTP请求体中设置高优先级(请注意,将消息设置为高优先级会导致相对于正常优先级的消息更加耗电)。

示例HTTP请求:

{
   "to" : "REGISTRATION_TOKEN",
    "notification" : {
     "sound" : "default",
     "badge" : "1",
     "title" : "default",
     "body"  : "Test",
   },
   "content_available" : true,
   "priority" : "normal",
 }

1
感谢您提供Postman的想法。我尝试使用您的POST请求,但它真的很奇怪:如果我发送请求,我会收到一个“错误:未注册”的消息,应用程序无法再接收通知(即使是在前台接收到的通知)。我必须生成一个新的GCM令牌才能使其再次工作,并且一旦我使用POST请求,它就会再次发生。您知道为什么吗? - Gannicus
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - ztan
是的,我确实在我的服务器上注册了好的令牌。让我困扰的是,POST请求的行为就像它取消了应用程序在GCM上的注册。 - Gannicus
当您的应用程序在后台运行时,您是否收到推送通知?您所说的取消注册GCM的应用程序是什么意思?只有在删除应用程序或调用deleteTokenWithAuthorizedEntity时,才能注销您的应用程序。 - ztan
1
我通过将 self.registrationOptions = @{kGGLInstanceIDRegisterAPNSOption:deviceToken, kGGLInstanceIDAPNSServerTypeSandboxOption:@NO}; 中的“NO”更改为“YES”,最终在后台接收到了消息。现在,我只需要按照您建议的在Java示例中进行更改,但问题是我只有一个JAR文件,因此无法访问Message类并对其进行修改。请问我该如何执行这个操作? - Gannicus
显示剩余9条评论

4

我曾经遇到同样的问题,当应用程序被杀死时无法接收主题通知,现在这个POST请求可以工作了,我必须添加高优先级。

   {
   "to" : "/topics/offers",
   "notification" : {
     "sound" : "default",
     "badge" : "1",
     "body" : "Text",
   },
     "priority" : "high",   
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接