Google Cloud Messaging:iOS App 在后台时不会收到警报

Posted

技术标签:

【中文标题】Google Cloud Messaging:iOS App 在后台时不会收到警报【英文标题】:Google Cloud Messaging: don't receive alerts when iOS App is in background 【发布时间】:2015-08-21 07:36:27 【问题描述】:

我已按照本教程https://developers.google.com/cloud-messaging/ios/client 在我的 iOS 应用程序上实现 GCM。我的应用服务器是一个用 Java 编写的谷歌应用引擎,我使用 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

有人能弄清楚我不在前台时没有收到通知吗?

EDIT:服务器端用于发送 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;

EDIT2: POST 请求的屏幕截图 http://image.noelshack.com/fichiers/2015/34/1440193492-gcm1.png http://image.noelshack.com/fichiers/2015/34/1440193502-gcm2.png

EDIT3:我现在从我的应用服务器发送的请求:

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();
    

【问题讨论】:

【参考方案1】:

基于GCM documentation,您可以将content_available设置为true

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

content_available对应苹果的content-available,你可以在this Apple Push Notification Service documentation找到。

此外,您应该使用Notification playload 向您的 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,

The Java library 只是一个示例,您可以在其中添加其他字段。比如Message.java类中,可以添加两个私有变量,一个是private final Boolean contentAvailable,另一个是private final Map&lt;String, String&gt; notification

您可以在终端中通过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 尝试HTTP 请求,或在Postman 中尝试。

已编辑:

如果您的应用程序已终止,并且您希望在您的设备中显示推送通知,您可以在 HTTP 请求正文中设置 high priority(请注意,将消息设置为高优先级有助于与正常优先级消息相比,电池消耗量)。

HTTP 请求示例:


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

【讨论】:

还有其他方法吗?因为我使用的 Java 库 (github.com/google/gcm) 似乎没有 content_available 字段或通知负载。我将编辑我的答案以放置我使用该库发送的消息的代码 sn-p。 感谢 Postman 的创意。我尝试使用您的 POST 请求,但这真的很奇怪:如果我发送请求,我会收到“错误:notRegistered”并且应用程序无法再接收通知(即使是我在前台收到的通知)。我必须生成一个新的 GCM 令牌才能使其再次工作,并且一旦我使用 POST 请求,它就会再次发生。你知道为什么吗? registration token 应该是每个设备每个应用程序每次安装都是唯一的,如果您的应用程序之前被删除并再次重新安装,它将生成一个新令牌,如果您调用 GGLInstanceID 方法.如果您将旧的registration token 存储在服务器或本地数据库中,则应在重新安装应用后将其更新为新的。 是的,我确实在我的服务器上注册了好的令牌。困扰我的是 POST 请求的行为就像从 GCM 注销应用程序一样。 我终于通过在self.registrationOptions = @kGGLInstanceIDRegisterAPNSOption:deviceToken, kGGLInstanceIDAPNSServerTypeSandboxOption:@NO; 中将“NO”更改为“YES”在后台收到消息。现在我只需要在 java 示例中进行您建议的更改,但问题是我只有一个 jar 文件,因此我无法访问 Message 类并对其进行修改。请问我该怎么做?【参考方案2】:

我有同样的问题,当应用被杀死时无法收到主题通知,这个 POST 正在工作,我必须添加优先级高。

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

【讨论】:

谢谢!你救了我的命:D

以上是关于Google Cloud Messaging:iOS App 在后台时不会收到警报的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Google Cloud Messaging 将通知推送到 iOS

iOS 和 Google Cloud Messaging:在一系列通知中缺少推送通知

Google Cloud Messaging Bridging Header 导入失败

使用 pod Google/Cloud Messaging 时 iTunes Store 应用程序验证错误

将旧代码中的 Firebase 数据库与 Google Cloud Messaging 结合使用

如何在 Chrome 中实现 Google Cloud Messaging - Topic Messaging?