web-dev-qa-db-fra.com

didReceiveRemoteNotification: fetchCompletionHandler n'est pas appelé lorsque l'application est en arrière-plan et n'est pas connectée à Xcode

J'ai un problème très étrange, j'ai implémenté:

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

Pour une notification Push à distance silencieuse. Cela fonctionne parfaitement lorsque l'application est en arrière-plan et connectée à Xcode. Lorsque je débranche un appareil iOS et exécute l'application, je passe en arrière-plan et j'envoie une notification à distance, didReceiveRemoteNotification:fetchCompletionHandler ne pas être appelé.

Mon code ci-dessous:

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

Et [self endBackgroundTask] est à la fin de la fonction cloudAcknowledge. Une idée de ce qui se passe ici?

MODIFIER:

La charge utile se présente comme suit: { aps = { "content-available" = 1; }; pushCode = 12; }

42
Idan Moshe

Le problème a été corrigé dans iOS 7.1 Beta 3. J'ai revérifié et je confirme que cela fonctionne très bien.

2
Idan Moshe

Il pourrait y avoir un certain nombre de choses qui auraient mal tourné, la première de ma propre expérience. Afin de faire fonctionner la notification Push silencieuse. Votre charge utile doit être structurée correctement,

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

Votre message Push contient-il content-available: 1 sinon, iOS n'appellera pas la nouvelle méthode déléguée.

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

La raison possible est que le rafraîchissement de l'application en arrière-plan est désactivé sur votre iPhone.
Vous pouvez activer/désactiver cette option dans Paramètres-> Général-> Actualisation de l'application en arrière-plan.
Lorsque la mise à jour de l'application en arrière-plan est désactivée sur votre téléphone, la méthode didReceiveRemoteNotification:fetchCompletionHandler Sera appelée uniquement lorsque le téléphone est connecté à XCode.

11
Stan

Je veux juste ajouter une réponse mise à jour.

Je fais face au même problème.

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

Ne reçoit pas d'appel lorsque l'application est supprimée du multitâche en arrière-plan (appuyez deux fois sur le bouton d'accueil et faites glisser vers le haut pour tuer l'application).

J'ai testé cela moi-même en utilisant la notification push de développement et l'outil NWPusher ( https: //github.com/noodlewerk/NWPusher )

Documentation obsolète

Ce bloc de documentation précédent qui dit:

Contrairement à l'application: didReceiveRemoteNotification: méthode, qui est appelée uniquement lorsque votre application est en cours d'exécution, le système appelle cette méthode quel que soit l'état de votre application . Si votre application est suspendue ou ne fonctionne pas, le système se réveille ou lance votre application et la met en état d'exécution en arrière-plan avant d'appeler la méthode. Si l'utilisateur ouvre votre application à partir de l'alerte affichée par le système, le système appelle à nouveau cette méthode afin que vous sachiez quelle notification l'utilisateur a sélectionnée.

Est obsolète (au moment de la rédaction de ce 04/06/2015).

Documentation mise à jour (au 04/06/2015)

J'ai vérifié la documentation (au moment de la rédaction de ce 04/06/2015), il est dit:

Utilisez cette méthode pour traiter les notifications distantes entrantes pour votre application. Contrairement à la méthode application: didReceiveRemoteNotification:, qui est appelée uniquement lorsque votre application s'exécute au premier plan, le système appelle cette méthode lorsque votre application s'exécute au premier plan ou fond . De plus, si vous avez activé le mode d'arrière-plan des notifications à distance, le système lance votre application (ou la sort de l'état suspendu) et la met en arrière-plan lorsqu'une notification à distance arrive. Cependant, le système ne lance pas automatiquement votre application si l'utilisateur l'a forcée à la quitter. Dans ce cas, l'utilisateur doit relancer votre application ou redémarrer l'appareil avant que le système ne tente de redémarrer automatiquement votre application.

https: //developer.Apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/index.html#//Apple_ref/occ/intfm/UIApplicationDelegate/application: didReceiveRemoteNotification: fetchC :

Si vous lisez attentivement, vous remarquerez qu'il dit maintenant:

le système appelle cette méthode lorsque votre application s'exécute au premier plan ou en arrière-plan .

NE PAS:

quel que soit l'état de votre application

Il semble donc qu'à partir d'iOS 8+, nous n'avons pas de chance :(

7
Zhang
TL;DR: Use Test Flight in iTunes Connect

Peut-être que certains d'entre vous l'ont déjà compris, mais je poste ici car je ne vois pas de réponse claire.

Ils avaient exactement le même problème à décrire. Les notifications Push silencieuses ont fonctionné lorsque le câble Lightning était connecté. A cessé de fonctionner lorsque j'ai débranché le câble. J'ai fait suivre chaque NSLog et appel réseau pour prouver que cela se passait bien.

J'utilisais la charge utile suggérée dans de nombreuses réponses, ainsi que celle-ci:

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

Après plusieurs heures, j'ai découvert que le problème était lié au développement ad hoc et profils d'approvisionnement, sur iOS 8.0 , 8.1 et 8.1.1 . J'utilisais Crashlytics pour envoyer des versions bêta de mon application (qui utilise Adhoc profil).

Le correctif est:

Pour le faire fonctionner, essayez l'intégration d'Apple Test Flight avec iTunes Connect. En utilisant cela, vous enverrez une archive de votre application (la même archive que celle utilisée sur l'App Store) mais vous permettez à votre binaire d'être utilisé en version bêta. La version installée à partir de Test Flight utilise probablement (je ne peux pas prouver) le même profil d'approvisionnement de production de l'App Store, et l'application fonctionne comme un charme.

Voici un lien qui aide à configurer le compte Test Flight:

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

Pas même une seule charge utile silencieuse n'a été manquée.

J'espère que cela aide quelqu'un à ne pas perdre une semaine entière sur cette question.

5
Gui Moura

Hé les gars, c'est très simple, vous pouvez appeler votre méthode

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

Pas:

  1. projet -> Capacités -> Modes d'arrière-plan et cochez les cases "Récupération d'arrière-plan" et "Notifications à distance", ou allez dans .plist et sélectionnez une ligne et nommez "Modes d'arrière-plan" et il créera avec un tableau, définissez "Article 0" avec la chaîne "Notifications à distance".

  2. dire au développeur côté serveur qu'il doit envoyer

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

c'est maintenant que vous pouvez appeler vos méthodes:

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

C'était un problème pour moi aujourd'hui et j'étais déconcerté.

iOS: 10.2.1 xCode: 8.2.1 Swift: 3.0.2

Les problèmes ne concernaient qu'un seul téléphone.

J'ai relu la documentation d'Apples Push au cas où j'aurais manqué quelque chose avec le nouveau framework UserNotifications et ou gâché quelque chose avec mon code pour revenir aux fonctions de délégué dépréciées pour iOS 9.

Quoi qu'il en soit, j'ai remarqué cette ligne dans la documentation deapplication:didReceiveRemoteNotification:fetchCompletionHandler::

"Les applications qui consomment beaucoup d'énergie lors du traitement des notifications à distance ne sont pas toujours réveillées tôt pour traiter les futures notifications."

C'est la toute dernière ligne de la page.

Bien que je souhaite Apple m'en a dit plus, il s'avère qu'un simple redémarrage du téléphone a résolu le problème pour moi. Je souhaite vraiment pouvoir comprendre exactement ce qui n'a pas fonctionné, mais voici mes conclusions très spéculatives:

1) Les notifications push n'étaient pas envoyées à cette application sur ce téléphone en raison de la ligne dans la documentation mentionnée ci-dessus.

2) Lorsqu'il est connecté à xCode, iOS ignore la règle documentée ci-dessus.

3) J'ai vérifié le calculateur de pourcentage de batterie (notoirement déroutant) dans les paramètres système. Il a montré mon application à un modeste 6%, MAIS Safari était un énorme 75% sur ce téléphone pour une raison quelconque.

4) Après le redémarrage du téléphone, Safari est revenu à environ 25%

5) Push a bien fonctionné après cela.

Alors ... Ma conclusion ultime. Pour éliminer le problème de batterie documenté, essayez de redémarrer le téléphone ou essayez un autre téléphone et voyez si le problème persiste.

3
Jon Vogel

Pour utiliser le téléchargement en arrière-plan dans le développement d'applications iOS, voici quelques points importants que nous devons suivre…

  1. Activez la touche UIBackgroundModes avec la valeur de notification à distance dans le fichier info.plist.

  2. Ensuite, implémentez la méthode ci-dessous dans votre fichier AppDelegate.

    application:didReceiveRemoteNotification:fetchCompletionHandler

Plus de détails: ios-7-true-multitasking

3
codercat

Code qui fonctionne pour récupérer les notifications à distance, activez la fonctionnalité de notifications à distance dans les modes d'arrière-plan et j'ai également la récupération en arrière-plan activée (je ne sais pas si c'est nécessaire) J'utilise ce code:

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

Code qui stocke la notification lorsqu'elle est en arrière-plan, la clé pour moi était de démarrer une tâche de téléchargement en arrière-plan pour me permettre de télécharger les informations afin de les stocker, puis lorsque l'application devient active, la méthode est déclenchée je vérifie s'il y a une notification manquante stockée pour le montrer.

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

}

Voici le code approprié pour télécharger les informations en arrière-plan à l'aide d'une tâche en arrière-plan:

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

Et bien c'est tout ce que je pense, je le poste car quelqu'un m'a demandé de poster une mise à jour, j'espère que ça peut aider quelqu'un ...

2

J'ai passé deux jours là-dessus! Avant de vérifier votre code et vos paramètres Push - vérifiez que vous n'êtes pas en MODE BASSE PUISSANCE !!! (et que le rafraîchissement de l'application en arrière-plan est activé) lorsque vous connectez votre appareil à l'alimentation xCode ==, cela fonctionnera, mais si vous le déconnectez - le mode basse consommation désactivera l'actualisation de l'application en arrière-plan.

2
DaNLtR