web-dev-qa-db-ja.com

didReceiveRemoteNotification:fetchCompletionHandlerは、アプリがバックグラウンドでXcodeに接続されていないときに呼び出されない

私は非常に奇妙な問題を抱えています、私は実装しました:

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

42
Idan Moshe

問題はiOS 7.1 Beta 3で修正されました。再確認したところ、問題なく動作していることを確認しました。

2
Idan Moshe

間違っているかもしれないものがいくつかある可能性があります。最初は私自身の経験からです。サイレントプッシュ通知を機能させるため。ペイロードは正しく構成する必要があり、

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

プッシュメッセージにcontent-available: 1そうでない場合、iOSは新しいデリゲートメソッドを呼び出しません。

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

考えられる理由は、iPhoneのバックグラウンドアプリの更新がオフになっていることです。
このオプションは、[設定]-> [全般]-> [バックグラウンドアプリの更新]でオン/オフにできます。
スマートフォンでバックグラウンドアプリの更新がオフの場合、didReceiveRemoteNotification:fetchCompletionHandlerメソッドは、電話がXCodeに接続されている場合にのみ呼び出されます。

11
Stan

更新された回答を追加したいだけです。

私は同じ問題に直面しています。

-(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日現在)

私はドキュメントをチェックしました(この2015年4月6日執筆時点)。

このメソッドを使用して、アプリの着信リモート通知を処理します。アプリがフォアグラウンドで実行されている場合にのみ呼び出されるapplication:didReceiveRemoteNotification:メソッドとは異なり、システムはアプリがforeground orbackgroundで実行されている場合にこのメソッドを呼び出します。さらに、リモート通知バックグラウンドモードを有効にした場合、システムはアプリを起動し(またはサスペンド状態からウェイクし)、リモート通知が到着するとバックグラウンド状態にします。ただし、ユーザーが強制終了した場合、システムはアプリを自動的に起動しません。その場合、ユーザーがアプリを再起動するか、デバイスを再起動してから、システムがアプリを自動的に起動します。

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

注意深く読むと、次のようになっていることに気付くでしょう。

アプリがforeground orbackgroundで実行されている場合、システムはこのメソッドを呼び出します。

しない:

アプリの状態に関係なく

だから、iOS 8+からは運が悪いようです:(

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

たぶん、あなた方の何人かはすでにこれを理解しているかもしれませんが、明確な答えが見当たらないので、ここに投稿します。

まったく同じ問題が記述されていました。 Lightningケーブルが接続されている間、サイレントプッシュ通知が機能していました。ケーブルを外したときに動作を停止しました。 NSLogとネットワークコールをすべて追跡して、それが実際に起こっていることを証明しました。

私は多くの答えで提案されたペイロードとこれを使用していました:

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

数時間後、この問題はAdhocおよびDevelopmentプロビジョニングプロファイル、iOS8.08.1および8.1.1Crashlyticsを使用してアプリのベータ版を送信していました(Adhocプロファイルを使用)。

修正方法:

動作させるには、iTunes ConnectとのAppleTest Flight統合を試してください。これを使用して、アプリのアーカイブ(App Storeで使用されるものと同じアーカイブ)を送信しますが、ベータ版でバイナリを使用できるようにします。 Test Flightからインストールされたバージョンは、おそらく(証明できませんが)App Storeの同じProduction Provisioning Profileを使用し、アプリは魅力のように機能します。

テストフライトアカウントのセットアップに役立つリンクを次に示します。

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

単一のサイレントペイロードも見逃されていません。

誰かがこの問題で1週間も失うことのないようになることを願っています。

5
Gui Moura

やあみんなあなたのメソッドを呼び出すことができます非常に簡単

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

手順:

  1. プロジェクト->機能->バックグラウンドモードを選択し、「バックグラウンドフェッチ」と「リモート通知」のチェックボックスを選択するか、.plistに移動して行を選択し、「バックグラウンドモード」という名前を付けると、配列で作成され、設定されます「リモート通知」という文字列を持つ「アイテム0」。

  2. 彼が送信する必要があることをサーバー側の開発者に言う

    「aps」:{「コンテンツ利用可能」:1}

これでメソッドを呼び出すことができます:

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

これは今日の私にとっての問題であり、私は当惑しました。

iOS:10.2.1 xCode:8.2.1 Swift:3.0.2

問題は1台の携帯電話でのみ発生し、xCodeに差し込まれた場合にのみパックされます。

新しいUserNotificationsフレームワークで何かを見逃した場合や、iOS 9の減価償却されたデリゲート関数にフォールバックするためにコードをめちゃくちゃにした場合に備えて、Apples Pushドキュメントを読み直します。

とにかく、私はこの行に気付きました ドキュメントでapplication:didReceiveRemoteNotification:fetchCompletionHandler:

「リモート通知を処理する際に大量の電力を使用するアプリは、将来の通知を処理するために常に早期に起動されるとは限りません。」

ページの最後の行です。

Appleがもっと教えてくれればいいのに、電話を再起動するだけで問題が解決したことがわかりました。何が間違っていたのかを正確に把握できたらと思っています。

1)上記のドキュメントの行のため、プッシュ通知はこの特定の電話のこのアプリに配信されていませんでした。

2)xCode iOSにプラグインすると、上記の文書化されたルールが無視されます。

3)システム設定で(悪名高いほど紛らわしい)バッテリー残量計算機をチェックしました。私のアプリは控えめな6%でしたが、Safariは何らかの理由でこの電話でなんと75%でした。

4)電話の再起動後、Safariは約25%に戻りました

5)その後、プッシュは正常に機能しました。

だから...私の究極の結論。文書化されたバッテリーの問題を取り除くには、電話の再起動を試すか、別の電話を試して問題が解決しないか確認します。

3
Jon Vogel

IOSアプリケーション開発でバックグラウンドプッシュダウンロードを使用するために、従う必要があるいくつかの重要なポイントがあります…

  1. Info.plistファイルでUIBackgroundModesキーをリモート通知値で有効にします。

  2. 次に、AppDelegateファイルに以下のメソッドを実装します。

    application:didReceiveRemoteNotification:fetchCompletionHandler

詳細: ios-7-true-multitasking

3
codercat

リモート通知をフェッチするコード、バックグラウンドモードでリモート通知機能を有効にし、バックグラウンドフェッチも有効にしています(必要かどうかわかりません)このコードを使用します:

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

これが私が思うすべてです。誰かが更新を投稿するように頼んだので、私はそれを投稿します、それが誰かを助けるかもしれないことを願っています...

これに2日費やしました!コードとプッシュパラメータを確認する前に、デバイスをxCode == powerに接続するときに低電力モードになっていないことを確認してください!!!(およびバックグラウンドアプリの更新がオンになっている)低電力モードでは、アプリのバックグラウンド更新が無効になります。

2
DaNLtR