Подтвердить что ты не робот

DidReceiveRemoteNotification: fetchCompletionHandler не вызывается, когда приложение находится в фоновом режиме и не связано с Xcode

У меня очень странная проблема, я реализовал:

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

Для бесшумного удаленного push-уведомления. Он отлично работает, когда приложение находится в фоновом режиме и подключено к 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. Любая идея, что, черт возьми, здесь происходит?

EDIT:

Полезная нагрузка выглядит следующим образом: { aps = { "content-available" = 1; }; pushCode = 12; }

4b9b3361

Ответ 1

Проблема исправлена ​​в iOS 7.1 Beta 3. Я дважды проверял, и я подтверждаю, что он работает нормально.

Ответ 2

Возможно, что-то могло произойти не так, первый из моего собственного опыта. Для того, чтобы сделать тихую работу уведомления. Ваша полезная нагрузка должна быть правильно структурирована,

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

Имеет ли ваше push-сообщение content-available: 1, если нет, тогда iOS не вызовет новый метод делегирования.

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

Ответ 3

TL;DR: Use Test Flight in iTunes Connect

Возможно, некоторые из вас, ребята, уже поняли это, но я отправляю здесь, так как не вижу ясного ответа.

В той же самой проблеме описывалась. Молчаливые push-уведомления срабатывали при подключении кабеля Lightning. Остановился, когда я отсоединил кабель. Я проверил каждый NSLog и сетевой вызов, чтобы доказать, что это действительно происходит.

Я использовал полезную нагрузку, предложенную во многих ответах, а также эту:

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

Через несколько часов я обнаружил, что проблема связана с профилями профилей Adhoc и Разработка, на iOS 8.0, 8.1 и 8.1.1. Я использовал Crashlytics для отправки бета-версий моего приложения (которое использует профиль Adhoc).

Исправление:

Чтобы он работал, попробуйте выполнить интеграцию Apple Test Flight с iTunes Connect. Используя это, вы отправите Архив своего приложения (тот же архив, который будет использоваться в App Store), но включите ваш бинарный файл в бета-версии. Версия, установленная из Test Flight, вероятно (я не могу доказать) использует тот же профиль Provisioning Profile из App Store, и приложение работает как прелесть.

Здесь ссылка, которая помогает настроить учетную запись Test Flight:

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

Не было пропущено ни одной Silent полезной нагрузки.

Я надеюсь, что это поможет кому-то не потерять целую неделю по этой проблеме.

Ответ 4

Возможная причина в том, что обновление фонового обновления отключено на вашем iPhone.
Вы можете включить или выключить эту опцию в меню Настройки- > Общие- > Обновление фона.
Когда на вашем телефоне отключено фоновое обновление приложения, метод didReceiveRemoteNotification:fetchCompletionHandler вызывается только тогда, когда телефон подключен к XCode.

Ответ 5

Просто хочу добавить обновленный ответ.

Я столкнулся с одной и той же проблемой.

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

Не вызывается, когда приложение убито из фоновой многозадачности (дважды нажмите кнопку "домой" и проведите по экрану, чтобы убить приложение).

Я сам протестировал это, используя уведомление о начале развертывания и инструмент NWPusher (https://github.com/noodlewerk/NWPusher)

Устаревшая документация

Этот предыдущий блок документации, который гласит:

В отличие от приложения: didReceiveRemoteNotification: метод, который вызывается только тогда, когда ваше приложение запущено, система вызывает этот метод независимо от состояния вашего приложения. Если ваше приложение приостановлено или нет работает, система просыпается или запускает ваше приложение и помещает его в перед запуском метода. Если пользователь открывает ваше приложение из системного предупреждения, система вызывает этот метод снова, чтобы вы знали, какое уведомление пользователь выбрал.

Устаревший (на момент написания этого 04/06/2015).

Обновленная документация (по состоянию на 04/06/2015)

Я проверил документацию (на момент написания этого 04/06/2015), он говорит:

Используйте этот метод для обработки входящих удаленных уведомлений для вашего приложения. В отличие от приложения: метод didReceiveRemoteNotification: метод, который вызывается только тогда, когда ваше приложение работает на переднем плане, система вызывает этот метод, когда ваше приложение работает на переднем плане или фон. Кроме того, если вы включили удаленные уведомления фоновый режим, система запускает ваше приложение (или пробуждает его из приостановленное состояние) и помещает его в фоновое состояние, когда удаленный уведомление приходит. Однако система не автоматически запустите свое приложение, если у пользователя есть сила - бросить его. В этой ситуации пользователь должен перезапустить приложение или перезапустить устройство перед системой снова пытается запустить приложение автоматически.

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

Если вы внимательно прочитаете, вы заметите, что теперь он говорит:

система вызывает этот метод, когда ваше приложение работает на переднем плане или фон.

НЕ

независимо от состояния вашего приложения

Итак, похоже, что из iOS 8+ нам не повезло: (

Ответ 6

Чтобы использовать Background Push Download в разработке приложений iOS, вот несколько важных моментов, которые нам нужно выполнить...

  • Включить ключ UIBackgroundModes с удаленным уведомлением в файле info.plist.

  • Затем выполните нижеприведенный метод в файле AppDelegate.

    application:didReceiveRemoteNotification:fetchCompletionHandler

Подробнее: ios-7-true-multitasking

Ответ 7

Эй, ребята, очень просто вы можете назвать свой метод

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

Шаги:

  • project → Capablities → Режимы фона и установите флажки "Фоновая выборка" и "Удаленные уведомления" или перейдите в .plist и выберите строку и укажите имя "Режимы фона", и она будет создана с массивом, установите "Item 0" со строкой "Удаленные уведомления",.

  • скажите разработчику на стороне сервера, что он должен отправить

    "aps": {        "контент-доступный": 1   }

thats теперь вы можете вызвать свои методы:

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

Ответ 8

Код, который использует выбор удаленных уведомлений, позволяет использовать функцию удаленных уведомлений в фоновых режимах, и у меня также включена функция fetch fetch (я не знаю, если это необходимо). Я использую этот код:

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

Ну, это все, что я думаю, я публикую его, потому что кто-то попросил меня опубликовать обновление, я надеюсь, что это может помочь кому-то...

Ответ 9

Сегодня это было проблемой для меня, и я был озадачен.

iOS: 10.2.1 xCode: 8.2.1 Swift: 3.0.2

Проблемы были только на одном телефоне, я бы упаковал только при подключении к xCode.

Я перечитываю документацию Apples push в случае, если я что-то пропустил с новой инфраструктурой UserNotifications и/или что-то перепутал с моим кодом, чтобы вернуться к амортизированным функциям делегата для iOS 9.

В любом случае, я заметил эту строку в документации для application:didReceiveRemoteNotification:fetchCompletionHandler::

"Приложения, которые используют значительную мощность при обработке удаленных уведомлений, не всегда могут разбудить раньше, чтобы обрабатывать будущие уведомления".

Это самая последняя строка на странице.

Пока я желаю, чтобы Apple рассказала мне больше, оказалось, что простой перезапуск телефона решил проблему для меня. Мне очень жаль, что я не смогу понять, что пошло не так, но вот мои очень умозрительные выводы:

1) Push-уведомления не были доставлены в это приложение на этом конкретном телефоне из-за строки в упомянутой выше документации.

2) При подключении к xCode iOS игнорирует описанное выше документированное правило.

3) Я проверил (заведомо запутанный) калькулятор процента батареи в системных настройках. Он показал мое приложение на скромных 6%, НО Safari была огромной 75% на этом телефоне по какой-то причине.

4) После перезагрузки телефона Safari вернулась примерно до 25%

5) После этого толчок работал нормально.

Итак... Мой окончательный вывод. Чтобы отсеять проблему с документированным аккумулятором, попробуйте перезагрузить телефон или попробуйте другой телефон и проверьте, не устранена ли проблема.

Ответ 10

Провел два дня на этом! Прежде чем проверять свой код и ваши параметры push - убедитесь, что вы не находитесь в режиме LOW POWER MODE!!! (и обновление фонового обновления включено) когда вы подключаете свое устройство к xCode ==, он будет работать, но если вы его отключите - режим низкого энергопотребления отключит обновление фонового приложения.