应用程序未运行时如何处理推送通知

46
当我的应用程序运行并收到推送通知时,如果我点击该通知,应用程序会启动-但然后它不会弹出我设置的警报视图,询问用户是否要查看通知的内容。它只是启动并保持原样。
当应用程序运行时 - 无论是作为活动应用程序还是在后台运行时 - 推送通知都能正常工作,但是应用程序运行时没有任何东西能正常工作。
我尝试在application: didFinishLaunchingWithOptions:中注销launchOptions NSDictionary以查看它带来了什么负载-但它显示为空值。因此,它基本上不包含任何内容-这没有意义,因为它不应该包含通知的负载吗?
有人有什么想法可以使推送通知在应用程序运行时到达时起作用?
编辑:这是我在application: didReceiveRemoteNotification中使用的代码,只是为了查看情况:
if (UIApplicationStateBackground) {

    NSLog(@"===========================");
    NSLog(@"App was in BACKGROUND...");
}
else if (UIApplicationStateActive == TRUE) {
    NSLog(@"===========================");
    NSLog(@"App was ACTIVE");
}
else {
    [[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");
}

这个代码本应该负责处理应用程序的所有状态——但它并没有。推送通知仅在应用程序在前台或后台运行时才能正常工作...

================================================

更新/编辑#2: 根据 "@dianz"(见下文)的建议,我修改了我的 application: didFinishLaunchingWithOptions 代码,并加入以下内容:

UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (localNotif) {
    NSString *json = [localNotif valueForKey:@"data"]; 

    UIAlertView *bam = [[UIAlertView alloc] initWithTitle:@"appDidFinishWithOptions"
                                                  message:json   
                                                 delegate:self
                                        cancelButtonTitle:@"cool"
                                        otherButtonTitles:nil];
    [bam show];

}
这确实让AlertView框出现了,但似乎没有有效载荷:AlertView的标题显示出来了(“appDidFinishWithOptions”),但json NSString为空...会继续调整...

======================

编辑#3-它现在工作得几乎完美
所以,在didFinishLaunchingWithOptions中:

UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
    if (localNotif) {
        // Grab the pushKey:
        pushKey = [localNotif valueForKey:@"pushKey"];
        // "pushKey" is an NSString declared globally
        // The "pushKey" in the "valueForKey" statement is part of my custom JSON Push Notification pay-load.
        // The value of pushKey will always be a String, which will be the name of the 
        // screen I want the App to navigate to. So when a Notification arrives, I go
        // through an IF statement and say "if pushKey='specials' then push the                                      
        // specialsViewController on, etc.

        // Here, I parse the "alert" portion of my JSON Push-Notification:
        NSDictionary *tempDict = [localNotif valueForKey:@"aps"];
        NSString *pushMessage = [tempDict valueForKey:@"alert"];


        // Finally, ask user if they wanna see the Push Notification or Cancel it:
        UIAlertView *bam = [[UIAlertView alloc] initWithTitle:@"(from appDidFinishWithOptions)"
                                                      message:pushMessage  
                                                     delegate:self
                                            cancelButtonTitle:@"Cancel"
                                            otherButtonTitles:@"View Info", nil];
        [bam show];

    }

我接下来实现了alertView:clickedButtonAtIndex方法,以查看用户在AlertView上选择了什么并相应地继续执行。

这个方法和didReceiveRemoteNotification的逻辑完美地配合起来。

但是……当应用程序没有运行时,如果我发送了一个推送通知,并且我没有立即点击推送通知警报,而是等待它消失(大约3-4秒后),然后我点击应用程序的图标 - 现在它有1个标记,应用程序启动,但当它启动时,我根本没有收到推送通知警报。它只是坐在那里。

我猜我需要找出那个排列组合……


2
你能否在不点击推送通知而直接点击应用程序图标时收到通知? - Vaibhav Saran
1
你是否已经解决了这个问题?我的意思是当应用程序处于未运行状态时如何处理推送通知?如果你收到很多通知,但没有打开应用程序,也没有点击系统的通知面板,那么你如何保留这些推送以便稍后检索? - Balram Tiwari
你是否已经成功获取了最后一个排列? - Vijay Tholpadi
这在iOS 8.X上能用吗?通过我的实验,似乎除非用户点击通知,否则内容将永远丢失(即不像存储并在用户启动应用程序时检索)。另外,我看到的奇怪之处是,即使我终止了应用程序并在此之后单击传入的通知,通知也会在以下方法中处理:- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler。 - Smart Home
你解决了吗?@sirab33,我也需要答案。当应用程序未运行且通知到来时,仍无法获取数据,而不需点击通知。有任何帮助吗? - Thiha Aung
@BalramTiwari 如果应用程序被挂起并且您收到了静默通知,则应用程序将保持唤醒状态30秒...如果应用程序已终止,则不会被唤醒。如果您单击应用程序(无论它是否已终止),它将调用didRecieveRemoteNotificaiton。如果您收到许多通知并且没有点击,则只需按照我之前说的做即可。 - mfaani
9个回答

37

当您的应用程序未运行或被杀死并且您点击推送通知时,将触发此函数;

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

所以你应该像这样处理它:

UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (localNotif) {
    NSString *json = [localNotif valueForKey:@"data"];
    // Parse your string to dictionary
}

1
我在 application:didFinishLaunchingWithOptions: 中做了非常类似的事情。我写了:NSDictionary *launchDic = [launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"]; NSLog(@"contains: %@", launchDic); - 从 NSLog 得到的输出是“(NULL)”。但我会像你一样使用 UILocalNotification 变量来尝试,而不是使用 NSDictionary 变量。也许这会有所不同。我稍后会发布我的结果... - sirab333
有趣 - 请查看我问题顶部的“EDIT”以获取您的建议刚刚做了什么的更新... - sirab333
dianz,我做了一些更改 - 请查看我的问题底部的EDIT#3(顶部)。 - sirab333
你是否已经解决了这个问题?我的意思是当应用程序处于未运行状态时如何处理推送通知?如果你收到很多通知,但没有打开应用程序,也没有点击系统的通知面板,那么你如何保留这些推送以便稍后检索? - Balram Tiwari
在iOS 10上运行良好。 - Martin Schultz

11

最好覆盖 didReceiveRemoteNotification 方法。

NSDictionary * pushNotificationPayload = [launchOptions valueForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if(pushNotificationPayload) {
    [self application:application didReceiveRemoteNotification:pushNotificationPayload];
}

这将再次触发didReceiveRemoteNotification方法,您的推送通知代码将像在应用程序运行时一样执行。


2
你能让这个工作起来吗?我的意思是当应用程序处于未运行状态时,如何处理推送通知?如果你收到很多通知却没有打开应用程序,也没有点击系统的通知面板,那么这些推送通知会如何保存以便以后检索呢? - Balram Tiwari
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法中 - Vaibhav Saran
1
你从哪里获取pushNotificationPayload? - Rob85
1
@Rob85 应该是 [self application:application didReceiveRemoteNotification:aPushNotification]; - Alex McPherson

7
我尝试了@dianz的答案,但对我没有用,不过我从这个答案中获得了帮助。
在我的情况下;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

NSDictionary *apnsBody = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (apnsBody) {
    // Do your code with apnsBody
}

5
尽管已经有答案了,但是提供的答案都没有正确地起作用。这是我的实现方法,我已经让它工作了。
这段代码放在application:didFinishLaunchingWithOptions:中。 Swift:
if let options = launchOptions, notification = options[UIApplicationLaunchOptionsRemoteNotificationKey] as? [NSObject : AnyObject] {
    // NOTE: (Kyle Begeman) May 19, 2016 - There is an issue with iOS instantiating the UIWindow object. Making this call without
    // a delay will cause window to be nil, thus preventing us from constructing the application and assigning it to the window.
    Quark.runAfterDelay(seconds: 0.001, after: {
        self.application(application, didReceiveRemoteNotification: notification)
    })
}

Objective-C:

if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
    [self application:application didReceiveRemoteNotification:launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]];
}

注意事项:

  • 由于iOS分配内存的方式,当应用程序被杀死后重新启动时,根UIApplication.sharedApplication().window对象为nil。添加1毫秒的延迟可以解决此问题。我没有在Objective-C中测试过。

  • 强制转换为[NSObject:AnyObject]是必需的,以避免类型冲突,但我没有进行过多的实验;在你自己的项目中实现时请注意这一点。Objective-C不需要这样做。

  • Quark只是我在所有项目中使用的一个有趣的小库,其中包含有用的和常用的扩展和方法。只需使用适合您应用程序需求的任何形式的延迟即可。


4

我曾经也遇到过同样的问题,但最终通过使用通知服务扩展程序,在应用程序处于非活动状态(未运行)时处理推送通知。这个解决方案是由苹果团队支持推荐的,并且仅适用于iOS 10,我已经实施并且它运作良好!我留下了链接:

https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ModifyingNotifications.html

这是步骤:
1. 为您的应用程序创建新的通知服务扩展。打开您的项目,按下文件 - 新建 - 目标,选择 iOS 部分,选取 "Notification Service Extension" 选项。输入扩展名和所需信息。之后,Xcode 将添加两个 Objective C 语言的文件: .h 和 .m 文件。
注意:您将使用三个标识符: 1)应用程序标识符(您已经拥有) 2)扩展标识符(您将创建) 3)App Group 标识符(您将创建)
  1. 需要启用应用组才能创建一个资源,可以保存和读取您的应用程序和扩展的信息。单击“显示项目导航器”。在目标列表中选择您的主项目。然后,单击“功能”并打开名为“App Groups”的选项。请添加标识符以识别共享资源的应用组。您应该对创建的扩展目标执行相同的步骤(选择扩展目标 - 功能 - 打开“App Groups” - 添加相同的应用组标识符)

  2. 您应该将扩展标识符添加到Apple Developer网站中,以识别通知服务扩展,还应该创建新的临时配置文件(开发,AdHoc和/或生产),并将其与新的临时配置文件关联。

  3. 在两个标识符(应用和扩展)上,您都应该编辑它们并在两者中启用“应用组”服务。您应该将应用组标识符添加到应用组服务中。

注意:应用标识符和扩展标识符应具有相同的应用组标识符。

  1. 在Xcode上下载新的 Provisional Profile 并将其与您的 Notification Service Extension 关联起来。请确保它们都正常。

  2. 之后,在您的应用程序和扩展的“功能”中,打开“App Groups”部分并更新它们。这三个步骤 - 1)将 App Groups 权利添加到您的权利文件中,2)将 App Groups 功能添加到您的 App ID 中,以及3)添加 App Groups 到您的 App ID - 应该被检查。

  3. 回到项目导航器并选择您的扩展程序文件夹。打开 .m 文件。您将看到一个名为 didReceiveNotificationRequest:(UNNotificationRequest *)request 的方法。在此方法中,您将创建一个不同的 NSUserDefaults,其中 SuiteName 完全等于应用组标识符,类似于:

    NSUserDefaults *defaultsGroup = [[NSUserDefaults alloc] initWithSuiteName: @"identifier for app group"];

  4. 在同一方法中,获取通知的主体并将其保存到 NSMutableArray 中,然后将其保存在共享资源中。就像这样:

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler{
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];

    NSMutableArray *notifications = [NSMutableArray new];
    NSUserDefaults *defaultsGroup = [[NSUserDefaults alloc] initWithSuiteName: @"Identifier for app group"];
    notifications = [[defaultsGroup objectForKey:@"notifications"] mutableCopy];
    if (notifications != nil){
        [notifications addObject:self.bestAttemptContent.userInfo];
    }else{
        notifications = [NSMutableArray new];
        [notifications addObject:self.bestAttemptContent.userInfo];
    }
    [defaultsGroup setObject:notifications forKey:@"notifications"];    
}
  1. 最后,在你的主项目的App Delegate中的didFinishLaunchingWithOptions方法中,使用以下代码将该数组恢复到共享资源中:
NSUserDefaults *defaultsGroup = [[NSUserDefaults alloc] initWithSuiteName: @"Identifier for app group"];
NSMutableArray *notifications = [[defaultsGroup objectForKey:@"notifications"] mutableCopy];
  1. 享受它!

我希望每个步骤都清晰明了。我将写一篇带有图像的文章来实现这个解决方案。另一个需要考虑的问题是它不适用于静默推送通知。


1

尝试进入didReceiveRemoteNotification方法并在那里处理它。在那里,您可以通过对applicationState进行条件语句来检查应用程序状态,例如检查它是否为UIApplicationStateActive或其他状态。


我正在处理applicationState - 我之前没有提到它是因为我不想让我的问题太长 - 但我会将那段代码添加到我在顶部的问题中,可以吗? - sirab333

0

使用以下推送负载进行推送接收:

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { }

此方法还将返回设备从Netmera接收到的最新推送对象,this way


0

试一下这个

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];

    //register notifications
    if([application respondsToSelector:@selector(registerUserNotificationSettings:)]) // ios 8
    {
        [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil]];
        [application registerForRemoteNotifications];
    }
    else // ios 7
    {
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge];
    }

    // open app from a notification
    NSDictionary *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];

    if (notification) 
    {
        //your alert should be here
    }

    // Set icon badge number to zero
    application.applicationIconBadgeNumber = 0;

    return YES;
}

0

Swift 3xCode 8.3.2

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // your code here

    // Check if app was not running in background and user taps on Push Notification
    // This will perform your code in didReceiveRemoteNotification where you should handle different application states
    if let options = launchOptions, let notification = options[UIApplicationLaunchOptionsKey.remoteNotification] as? [NSObject : AnyObject] {
        self.application(application, didReceiveRemoteNotification: notification)
    }

    return true
}

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