didReceiveRemoteNotification:当应用程序处于后台并且未连接到Xcode时未调用fetchCompletionHandler

Posted

技术标签:

【中文标题】didReceiveRemoteNotification:当应用程序处于后台并且未连接到Xcode时未调用fetchCompletionHandler【英文标题】:didReceiveRemoteNotification:fetchCompletionHandler not being called when app is in background and not connected to Xcode 【发布时间】:2013-12-23 10:31:20 【问题描述】:

我有一个很奇怪的问题,我实现了:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler

用于静默远程推送通知。 当应用程序在后台并连接到 Xcode 时,它​​可以完美运行。 当我拔下任何 ios 设备并运行应用程序时,转到后台并发送远程通知,didReceiveRemoteNotification:fetchCompletionHandler 不会被调用。

我的代码如下:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler 
    NSInteger pushCode = [userInfo[@"pushCode"] integerValue];
    NSLog(@"Silent Push Code Notification: %i", pushCode);
    NSDictionary *aps = userInfo[@"aps"];
    NSString *alertMessage = aps[@"alert"];

    if (pushCode == kPushCodeShowText) 
        UILocalNotification *localNotif = [[UILocalNotification alloc] init];
        localNotif.fireDate = [NSDate date];
        localNotif.timeZone = [NSTimeZone defaultTimeZone];
        localNotif.alertBody = alertMessage;
        localNotif.alertAction = @"OK";
        localNotif.soundName = @"sonar.aiff";
        // localNotif.applicationIconBadgeNumber = 0;
        localNotif.userInfo = nil;
        [[UIApplication sharedApplication] presentLocalNotificationNow:localNotif];

        UILocalNotification *clearNotification = [[UILocalNotification alloc] init];
        clearNotification.fireDate = [NSDate date];
        clearNotification.timeZone = [NSTimeZone defaultTimeZone];
        clearNotification.applicationIconBadgeNumber = -1;
        [[UIApplication sharedApplication] presentLocalNotificationNow:clearNotification];
    
    else  if (pushCode == kPushCodeLogOut) 
        [[MobileControlService sharedService] logoutUser];
        [[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode];
    
    else if (pushCode == kPushCodeSendLocation) 
        [[MobileControlService sharedService] saveLocation];
    
    else if (pushCode == kPushCodeMakeSound) 
        [[MobileControlHandler sharedInstance] playMobileControlAlertSound];
        // [[MobileControlHandler sharedInstance] makeAlarm];
        [[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode];
    
    else if (pushCode == kPushCodeRecordAudio) 
        if ([MobileControlHandler sharedInstance].isRecordingNow) 
            [[MobileControlHandler sharedInstance] stopRecord];
         else 
            [[MobileControlHandler sharedInstance] startRecord];
        
        [[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode];
    
    completionHandler(UIBackgroundFetchResultNewData);


- (void)saveLocation 
    bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    ];

    char *hostname;
    struct hostent *hostinfo;
    hostname = "http://m.google.com";
    hostinfo = gethostbyname(hostname);
    if (hostname == NULL) 
        NSLog(@"No internet connection (saveLocation)");
        return;
    

    if (self.locationManager.location.coordinate.latitude == 0.0 || self.locationManager.location.coordinate.longitude == 0.0) 
        NSLog(@"saveLocation - coordinates are 0.0.");
        return;
    

    NSLog(@"saveLocation - trying to get location.");
    NSString *postBody = [NSString stringWithFormat:@"Lat=%@&Lon=%@&Date=%@&userID=%@&batteryLevel=%@&version=%@&accuracy=%@&address=%@", self.myInfo.lat, self.myInfo.lon, self.myInfo.date, self.myInfo.userID, self.myInfo.batteryLevel, self.myInfo.version, self.myInfo.accuracy, self.myInfo.address];

NSURL *completeURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/saveLocation", WEB_SERVICES_URL]];
NSData *body = [postBody dataUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:completeURL];
[request setHTTPMethod:@"POST"];
[request setValue:kAPP_PASSWORD_VALUE forHTTPHeaderField:kAPP_PASSWORD_KEY];
[request setHTTPBody:body];
[request setValue:[NSString stringWithFormat:@"%d", body.length] forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];


if (__iOS_7_And_Heigher) 
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
        if (error) 
            NSLog(@"saveLocation Error: %@", error.localizedDescription);
         else 
            NSString *responseXML = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"\n\nResponseXML(saveLocation):\n%@", responseXML);
            [self cloudAcknowledge_whoSend:kPushCodeSendLocation];
        
    ];
    [dataTask resume];

else 
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) 
        if (connectionError) 
            NSLog(@"saveLocation Error: %@", connectionError.localizedDescription);
         else 
            NSString *responseXML = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"\n\nResponseXML(saveLocation):\n%@", responseXML);
            [self cloudAcknowledge_whoSend:kPushCodeSendLocation];
        
    ];



- (void)startBackgroundTask 
    bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    ];


- (void)endBackgroundTask 
    if (bgTask != UIBackgroundTaskInvalid) 
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    

[self endBackgroundTask]cloudAcknowledge 函数的末尾。 知道这里到底发生了什么吗?

编辑:

有效载荷是这样的: aps = "content-available" = 1; ; pushCode = 12;

【问题讨论】:

我前段时间正在研究这个问题,如果应用程序不活动(向上滑动),iOS 7 中的静默推送通知似乎会被忽略。这是开发人员与 Apple 支持人员交谈并得到答案的线程:devforums.apple.com/thread/209664?tstart=0 我仍然不确定在那之后如何继续。 :// 发送带有文本和声音的推送通知有效,但如果应用程序未连接到 xcode 并运行,则数据不会传递到应用程序。更糟糕的是,我什至在另一个项目中尝试过,我也遇到了同样的问题。 在 devforums 中发现的一条较新的评论说 iOS 7.1 beta 3 解决了这个问题。 devforums.apple.com/thread/210498?tstart=0 所以在 2014 年 3 月之后(或者 iOS 7.1 发布时)一切都会好起来的... -_- 你们中的任何人都测试过它是否适用于生产配置文件? 连接到 Xcode 时没问题。我的应用程序在后台和生产中不会触发委托方法。有解决办法吗? 【参考方案1】:

可能有很多事情可能出错了,第一个是我自己的经验。为了使静默推送通知工作。您的有效载荷必须结构正确,


    "aps" : 
        "content-available" : 1
    ,
    "data-id" : 345

你的推送消息是否有content-available: 1,如果没有,iOS 将不会调用新的委托方法。

- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler

【讨论】:

我的有效载荷是: aps = "content-available" = 1; ;推码 = 12; 当应用程序在后台并连接到 xcode 时效果很好。 didReceiveRemoteNotification:fetchCompletionHandler 没有被实时调用。我的朋友也有同样的问题。 由于某种原因,我的有效载荷必须包含声音。否则应用程序进入后台时将没有响应。虽然应用程序处于活动状态时会有一个。 这里的 iOS 8.1 也有同样的问题。 是的。确认的。从 8.0 开始就失败了【参考方案2】:

可能的原因是您的 iPhone 上的后台应用刷新已关闭。 您可以在“设置”->“常规”->“后台应用刷新”中打开/关闭此选项。 当手机关闭后台应用刷新时,didReceiveRemoteNotification:fetchCompletionHandler 方法只有在手机连接到 XCode 时才会被调用。

【讨论】:

你知道你今天救了一条命吗?【参考方案3】:

只想添加一个更新的答案。

我也面临同样的问题。

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;

当应用被后台多任务杀死时不会被调用(双击主页按钮并向上滑动以杀死应用)。

我自己使用开发推送通知和 NWPusher 工具 (https://github.com/noodlewerk/NWPusher) 对此进行了测试

过时的文档

之前的文档块说:

与 application:didReceiveRemoteNotification: 方法不同,它是 仅在您的应用运行时调用,系统调用此方法 无论您的应用处于何种状态。如果您的应用程序被暂停 运行时,系统唤醒或启动您的应用程序并将其放入 调用方法前的后台运行状态。如果用户打开 您的应用程序从系统显示的警报中,系统调用此方法 再次,以便您知道用户选择了哪个通知。

已过时(在撰写本文时 2015 年 4 月 6 日)。

更新的文档(截至 2015 年 4 月 6 日)

我检查了文档(在撰写本文时 04/06/2015),它说:

使用此方法为您的应用处理传入的远程通知。 与 application:didReceiveRemoteNotification: 方法不同,它是 仅当您的应用程序在前台运行时调用,系统 当您的应用在前台或运行时调用此方法 背景。此外,如果您启用了远程通知 后台模式,系统启动您的应用程序(或从 挂起状态),并在远程时将其置于后台状态 通知到达。但是系统不会自动 如果用户强制退出它,则启动您的应用程序。在这种情况下, 用户必须在系统之前重新启动您的应用程序或重新启动设备 尝试再次自动启动您的应用。

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/index.html#//apple_ref/occ/intfm/UIApplicationDelegate/application:didReceiveRemoteNotification:fetchCompletionHandler:

如果你仔细阅读,你会发现它现在写着:

系统 当您的应用在前台或运行时调用此方法 背景

不是:

无论您的应用处于何种状态

所以从 iOS 8+ 看来我们不走运:(

【讨论】:

谢谢,这有帮助!! "如果您启用了远程通知后台模式,系统将启动您的应用程序(或将其从挂起状态唤醒)并在远程通知到达时将其置于后台状态。我已启用此还是不行。为什么? 您可以通过在 Xcode 中使用调试器启动和停止应用程序来解决强制退出问题,之后它将在后台启动。【参考方案4】:
TL;DR: Use Test Flight in iTunes Connect

也许你们中的一些人已经想到了这一点,但我在这里发布,因为我没有看到明确的答案。

有完全相同的问题描述。连接 Lightning 电缆时,静默推送通知工作。当我断开电缆时停止工作。我跟踪了每个 NSLog 和网络调用,以证明这确实发生了。

我正在使用许多答案中建议的有效负载,以及这个:


    "aps" : 
        "content-available": 1,
        sound: ""
    

几个小时后,我发现问题与 iOS 8.08.1 上的 AdhocDevelopment 配置文件有关8.1.1。我正在使用 Crashlytics 发送我的应用的测试版(使用 Adhoc 配置文件)。

修复方法是:

为了让它正常工作,请尝试 Apple 的 Test Flight 与 iTunes Connect 的集成。使用它,您将发送应用程序的存档(与 App Store 上使用的存档相同),但使您的二进制文件能够在 beta 中使用。从 Test Flight 安装的版本可能(我无法证明)使用来自 App Store 的相同 Production Provisioning Profile,并且该应用的运行方式非常棒。

以下链接可帮助您设置试飞帐户:

http://code.tutsplus.com/tutorials/ios-8-beta-testing-with-testflight--cms-22224

甚至没有遗漏一个静默有效载荷。

我希望这可以帮助某人不要在这个问题上浪费整整一周的时间。

【讨论】:

我刚刚用 8.1.3 测试了这个,同样的问题出现在调试和应用商店版本中。【参考方案5】:

今天这对我来说是个问题,我很困惑。

iOS:10.2.1 x代码:8.2.1 斯威夫特:3.0.2

问题只在一部手机上,我只有在插入 xCode 时才能打包。

我重新阅读 Apple 推送文档,以防我错过了新的 UserNotifications 框架的某些内容,或者我的代码搞砸了某些内容以回退到 iOS 9 的折旧委托函数。

不管怎样,我注意到这条线in the documentation forapplication:didReceiveRemoteNotification:fetchCompletionHandler:

“在处理远程通知时使用大量电力的应用程序可能并不总是被提早唤醒以处理未来的通知。”

这是页面上的最后一行。

虽然我希望 Apple 告诉我更多信息,但事实证明,简单的手机重启解决了我的问题。我真的希望我能弄清楚到底出了什么问题,但这是我的推测性结论:

1) 由于上述文档中的一行,推送通知未发送到此特定手机上的此应用。

2) 插入 xCode 时,iOS 会忽略上述记录规则。

3) 我在系统设置中检查了(臭名昭著的)电池百分比计算器。它显示我的应用程序只有 6%,但出于某种原因,这款手机上的 Safari 浏览器高达 75%。

4) 手机重启后,Safari 又回落到 25% 左右

5) 之后推送工作正常。

所以...我的最终结论。要清除记录在案的电池问题,请尝试重启手机或尝试使用其他手机,看看问题是否仍然存在。

【讨论】:

【参考方案6】:

要在 iOS 应用程序开发中使用后台推送下载,以下是我们需要遵循的一些要点……

    在 info.plist 文件中启用带有远程通知值的 UIBackgroundModes 键。

    然后在您的 AppDelegate 文件中实现以下方法。

    application:didReceiveRemoteNotification:fetchCompletionHandler

更多详情:ios-7-true-multitasking

【讨论】:

我多次阅读此文档,我知道它应该以这种方式工作,因为当设备连接到 xcode 并在后台运行时它工作得很好。我仔细检查了很多次,相信我...... 'NSUserNotificationCenter ' 如果仅适用于 mac。【参考方案7】:

这很简单。你可以调用你的方法

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler 

步骤:

    项目-->能力-->后台模式 并选择“后台获取”和“远程通知”复选框,或进入.plist并选择一行并命名为“后台模式”,它将使用数组创建,使用字符串“远程通知”设置“项目0” .

    告诉服务器端开发者他应该发送

    “aps”: “内容可用”:1

现在你可以调用你的方法了:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler 

【讨论】:

【参考方案8】:

问题已在 iOS 7.1 Beta 3 中得到修复。 我仔细检查并确认它工作正常。

【讨论】:

我的iOS 7.1.1也是这样,不知道是不是只是App用开发配置文件编译时出现问题,而使用生产证书时可以正常工作? 我还没有在生产环境中对其进行测试。 我会在一周内告诉你 :-) 我在 iOS 7.1.2 上看到了相同的行为。关于这是否仅在开发配置文件中发生的任何更新? 它适用于较新版本的 iOS,我已启用远程通知功能,我将使用我的代码发布答案,抱歉忘记更新...【参考方案9】:

花了两天时间!在检查您的代码和您的推送参数之前 - 检查您是否处于低功耗模式!!!(并且后台应用程序刷新已打开) 当您将设备连接到 xCode==power 时,它会工作,但如果您断开它 - 低功耗模式将禁用后台应用刷新。

【讨论】:

这是添加“低功耗”模式可能导致此问题的唯一答案。但低功耗模式和后台应用刷新可以单独配置,因此,并非总是低功耗模式会禁用后台应用刷新。只是说。【参考方案10】:

用于获取远程通知的代码,在后台模式下启用远程通知功能,并且我也启用了后台获取(我不知道是否有必要)我使用此代码:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
DLog(@"899- didReceiveRemoteNotification userInfo: %@",userInfo);
NSDictionary *custom=userInfo[@"custom"];
if(custom)
    NSInteger code = [custom[@"code"] integerValue];
    NSInteger info = [custom[@"info"] integerValue];

    NSDictionary *messageInfo = userInfo[@"aps"];

    [[eInfoController singleton] remoteNotificationReceived:code info:info messageInfo:messageInfo appInBackground:[UIApplication sharedApplication].applicationState==UIApplicationStateBackground];

    handler(UIBackgroundFetchResultNewData);



- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
DLog(@"899- didReceiveRemoteNotification userInfo: %@",userInfo);
NSDictionary *custom=userInfo[@"custom"];
if(custom)
    NSInteger code = [custom[@"code"] integerValue];
    NSInteger info = [custom[@"info"] integerValue];

    NSDictionary *messageInfo = userInfo[@"aps"];

    [[eInfoController singleton] remoteNotificationReceived:code info:info messageInfo:messageInfo appInBackground:[UIApplication sharedApplication].applicationState==UIApplicationStateBackground];




- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken

//NSLog(@"My token is: %@", deviceToken);
const unsigned *tokenBytes = (const unsigned *)[deviceToken bytes];
NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
                      ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
                      ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
                      ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];

[[eInfoController singleton] setPushNotificationToken:hexToken];


- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error

NSLog(@"Failed to get token, error: %@", error);

在后台存储通知的代码,对我来说,关键是启动后台下载任务以允许我下载信息以存储它,然后当应用程序变为活动状态时触发方法我检查是否有缺少存储以显示它的通知。

-(void)remoteNotificationReceived:(NSInteger)code info:(NSInteger)info messageInfo:(NSDictionary*)messageInfo appInBackground:(BOOL)appInBackground

DLog(@"Notification received appInBackground: %d,pushCode: %ld, messageInfo: %@",appInBackground, (long)code,messageInfo);

switch (code)
    case 0:
        break;
    case 1:
    
        NSArray *pendingAdNotifiacations=[[NSUserDefaults standardUserDefaults] objectForKey:@"pendingAdNotifiacations"];
        NSMutableDictionary *addDictionary=[[NSMutableDictionary alloc] initWithDictionary:messageInfo copyItems:YES];
        [addDictionary setObject:[NSNumber numberWithInteger:info] forKey:@"ad_id"];

        if(!pendingAdNotifiacations)
            pendingAdNotifiacations=[NSArray arrayWithObject:addDictionary];
        else
            pendingAdNotifiacations=[pendingAdNotifiacations arrayByAddingObject:addDictionary];
        
        [addDictionary release];

        [[NSUserDefaults standardUserDefaults] setObject:pendingAdNotifiacations forKey:@"pendingAdNotifiacations"];
        [[NSUserDefaults standardUserDefaults] synchronize];
        DLog(@"pendingAdNotifiacations received: %@.",pendingAdNotifiacations);
        [[UIApplication sharedApplication] setApplicationIconBadgeNumber:(pendingAdNotifiacations)?[pendingAdNotifiacations count]:0];
        DLog(@"783- pendingAdNotifiacations: %lu.",(unsigned long)((pendingAdNotifiacations)?[pendingAdNotifiacations count]:0));
        if(appInBackground)

            [AdManager requestAndStoreAd:info];
        else
            [AdManager requestAndShowAd:info];
        
    
        break;
    default:
        break;



这是使用后台任务在后台下载信息的相关代码:

-(void)requestAdinBackgroundMode:(NSInteger)adId
DLog(@"744- requestAdinBackgroundMode begin");
if(_backgroundTask==UIBackgroundTaskInvalid)
    DLog(@"744- requestAdinBackgroundMode begin dispatcher");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
    DLog(@"744- passed dispatcher");
    [self beginBackgroundUpdateTask];

    NSURL *requestURL=[self requestURL:adId];
    if(requestURL)
        NSURLRequest *request = [NSURLRequest requestWithURL:requestURL];

        NSURLResponse * response = nil;
        NSError  * error = nil;
        DLog(@"744- NSURLConnection url: %@",requestURL);
        NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];

        if(NSClassFromString(@"NSJSONSerialization"))
        
            NSError *error = nil;
            id responseObject = [NSJSONSerialization
                     JSONObjectWithData:responseData
                     options:0
                     error:&error];

            if(error) 
                NSLog(@"JSON reading error: %@.",[error localizedDescription]);
                /* JSON was malformed, act appropriately here */ 
            else

            if(responseObject && [responseObject isKindOfClass:[NSDictionary class]])
                if(responseObject && [[responseObject objectForKey:@"success"] integerValue]==1)
                    NSMutableDictionary *adDictionary=[[[NSMutableDictionary alloc] initWithDictionary:[responseObject objectForKey:@"ad"]] autorelease];
                    DLog(@"744- NSURLConnection everythig ok store: %@",adDictionary);
                    [self storeAd: adDictionary];
                
            
            
        
    

    // Do something with the result

    [self endBackgroundUpdateTask];
);

 

- (void) beginBackgroundUpdateTask

DLog(@"744- requestAdinBackgroundMode begin");
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
    [self endBackgroundUpdateTask];
];


- (void) endBackgroundUpdateTask

DLog(@"744- End background task");
[[UIApplication sharedApplication] endBackgroundTask: _backgroundTask];
_backgroundTask = UIBackgroundTaskInvalid;

这就是我的想法,我发布它是因为有人要求我发布更新,我希望它可以帮助某人......

【讨论】:

以上是关于didReceiveRemoteNotification:当应用程序处于后台并且未连接到Xcode时未调用fetchCompletionHandler的主要内容,如果未能解决你的问题,请参考以下文章

尽管 content_available = true,但没有在后台调用 didReceiveRemoteNotification