web-dev-qa-db-ja.com

iOSのGoogleクラウドメッセージング(GCM)がリモート通知を受信しない

問題:
iOSはGCMからリモート通知を受信して​​いませんが、これが当てはまる理由に関する情報を見つけることができません。
プッシュ通知を初めて実装するとき、問題の原因が何であるかわからない。

状況:
現在、プッシュ通知にGCMを使用するiOSバージョンのアプリに取り組んでいます。通知はAndroidで正常に受信されていますが、iOSではまったく受信されていないようです。

アプリケーションを実行すると、コンソールに、すべてが正常で、トークンがあり、GCMに接続され、トピックにサブスクライブされていることが示されます。

app [579:45511]登録トークン:bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1pLTQ/8t-5QNiXbYwZYEWiSFD-frQKlsV8lgI
app [579:45511] GCMに接続しました
app [579:45511]すでに/ topics/globalにサブスクライブしています

ただし、通知を受信せず、通知センターをプルダウンするか、コントロールセンターをプルアップすると、コンソールに次のメッセージが表示されます。

app [579:45511] GCMに接続できませんでした:操作を完了できませんでした。 (com.google.gcmエラー2001。)

参照する以外はあまり教えてくれません...

/// KeyPairがありません。
kGGLInstanceIDOperationErrorCodeMissingKeyPair = 2001、

一方、マルチタスク機能を使用してバックグラウンドに送信して戻すと、次のようになります。

app [579:45511] GCMに接続しました
app [579:45511]すでに/ topics/globalにサブスクライブしています

設定:
GCMの指示に従ってiOSでセットアップし、実装についてはGcmExample.xcodeprojを参照しました(コードがまったく同じになるまで)。

Info.plistを「必要なバックグラウンドモード」に設定します->「アプリはプッシュ通知に応答してコンテンツをダウンロードします」

GCMとIPがホワイトリストに登録されていないという別のスタックオーバーフローの質問(現在は見つかりません)に出くわしましたが、問題ではないことを除外しました。

コード:

#import "AppDelegate.h"

@interface AppDelegate ()

@property(nonatomic, strong) void (^registrationHandler) (NSString *registrationToken, NSError *error);
@property(nonatomic, assign) BOOL connectedToGCM;
@property(nonatomic, strong) NSString* registrationToken;
@property(nonatomic, assign) BOOL subscribedToTopic;

@end

NSString *const SubscriptionTopic = @"/topics/global";

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  // Override point for customization after application launch.

  // [START_EXCLUDE]
  _registrationKey = @"onRegistrationCompleted";
  _messageKey = @"onMessageReceived";
  // Configure the Google context: parses the GoogleService-Info.plist, and initializes
  // the services that have entries in the file
  NSError* configureError;
  [[GGLContext sharedInstance] configureWithError:&configureError];
  if (configureError != nil) {
    NSLog(@"Error configuring the Google context: %@", configureError);
  }
  _gcmSenderID = [[[GGLContext sharedInstance] configuration] gcmSenderID];
  // [END_EXCLUDE]
  // Register for remote notifications
  UIUserNotificationType allNotificationTypes =
  (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
  UIUserNotificationSettings *settings =
  [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil];
  [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
  [[UIApplication sharedApplication] registerForRemoteNotifications];
  // [END register_for_remote_notifications]
  // [START start_gcm_service]
  [[GCMService sharedInstance] startWithConfig:[GCMConfig defaultConfig]];
  // [END start_gcm_service]
  __weak typeof(self) weakSelf = self;
  // Handler for registration token request
  _registrationHandler = ^(NSString *registrationToken, NSError *error){
    if (registrationToken != nil) {
      weakSelf.registrationToken = registrationToken;
      NSLog(@"Registration Token: %@", registrationToken);
      [weakSelf subscribeToTopic];
      NSDictionary *userInfo = @{@"registrationToken":registrationToken};
      [[NSNotificationCenter defaultCenter] postNotificationName:weakSelf.registrationKey
                                                          object:nil
                                                        userInfo:userInfo];
    } else {
      NSLog(@"Registration to GCM failed with error: %@", error.localizedDescription);
      NSDictionary *userInfo = @{@"error":error.localizedDescription};
      [[NSNotificationCenter defaultCenter] postNotificationName:weakSelf.registrationKey
                                                          object:nil
                                                        userInfo:userInfo];
    }
  };

  [[NSNotificationCenter defaultCenter] postNotificationName:_messageKey
                                                      object:nil
                                                    userInfo:nil];
  return YES;
}

- (void)subscribeToTopic {
  // If the app has a registration token and is connected to GCM, proceed to subscribe to the
  // topic
  if (_registrationToken && _connectedToGCM) {
    [[GCMPubSub sharedInstance] subscribeWithToken:_registrationToken
                                             topic:SubscriptionTopic
                                           options:nil
                                           handler:^(NSError *error) {
                                             if (error) {
                                               // Treat the "already subscribed" error more gently
                                               if (error.code == 3001) {
                                                 NSLog(@"Already subscribed to %@",
                                                       SubscriptionTopic);
                                               } else {
                                                 NSLog(@"Subscription failed: %@",
                                                       error.localizedDescription);
                                               }
                                             } else {
                                               self.subscribedToTopic = true;
                                               NSLog(@"Subscribed to %@", SubscriptionTopic);
                                             }
                                           }];
  }
}

- (void)applicationWillResignActive:(UIApplication *)application {
  // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
  // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

// [START disconnect_gcm_service]
- (void)applicationDidEnterBackground:(UIApplication *)application {
  // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
  // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.

  [[GCMService sharedInstance] disconnect];
  // [START_EXCLUDE]
  _connectedToGCM = NO;
  // [END_EXCLUDE]
}
// [END disconnect_gcm_service]

- (void)applicationWillEnterForeground:(UIApplication *)application {
  // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
  // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.

  // Connect to the GCM server to receive non-APNS notifications
  [[GCMService sharedInstance] connectWithHandler:^(NSError *error) {
    if (error) {
      NSLog(@"Could not connect to GCM: %@", error.localizedDescription);
    } else {
      _connectedToGCM = true;
      NSLog(@"Connected to GCM");
      // [START_EXCLUDE]
      [self subscribeToTopic];
      // [END_EXCLUDE]
    }
  }];
}
// [END connect_gcm_service]

- (void)applicationWillTerminate:(UIApplication *)application {
  // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}


// [START receive_apns_token]
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  // [END receive_apns_token]
  // [START get_gcm_reg_token]
  // Start the GGLInstanceID shared instance with the default config and request a registration
  // token to enable reception of notifications
  [[GGLInstanceID sharedInstance] startWithConfig:[GGLInstanceIDConfig defaultConfig]];
  _registrationOptions = @{kGGLInstanceIDRegisterAPNSOption:deviceToken,
                           kGGLInstanceIDAPNSServerTypeSandboxOption:@YES};
  [[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:_gcmSenderID
                                                      scope:kGGLInstanceIDScopeGCM
                                                    options:_registrationOptions
                                                    handler:_registrationHandler];
  // [END get_gcm_reg_token]
}

// [START receive_apns_token_error]
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
  NSLog(@"Registration for remote notification failed with error: %@", error.localizedDescription);
  // [END receive_apns_token_error]
  NSDictionary *userInfo = @{@"error" :error.localizedDescription};
  [[NSNotificationCenter defaultCenter] postNotificationName:_registrationKey
                                                      object:nil
                                                    userInfo:userInfo];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
  NSLog(@"Notification received: %@", userInfo);
  // This works only if the app started the GCM service
  [[GCMService sharedInstance] appDidReceiveMessage:userInfo];
  // Handle the received message
  // [START_EXCLUDE]
  [[NSNotificationCenter defaultCenter] postNotificationName:_messageKey
                                                      object:nil
                                                    userInfo:userInfo];
  // [END_EXCLUDE]
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))handler {
  NSLog(@"Notification received: %@", userInfo);
  // This works only if the app started the GCM service
  [[GCMService sharedInstance] appDidReceiveMessage:userInfo];
  // Handle the received message
  // Invoke the completion handler passing the appropriate UIBackgroundFetchResult value
  // [START_EXCLUDE]
  [[NSNotificationCenter defaultCenter] postNotificationName:_messageKey
                                                      object:nil
                                                    userInfo:userInfo];
  handler(UIBackgroundFetchResultNoData);
  // [END_EXCLUDE]
}
// [END ack_message_reception]

// [START on_token_refresh]
- (void)onTokenRefresh {
  // A rotation of the registration tokens is happening, so the app needs to request a new token.
  NSLog(@"The GCM registration token needs to be changed.");
  [[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:_gcmSenderID
                                                      scope:kGGLInstanceIDScopeGCM
                                                    options:_registrationOptions
                                                    handler:_registrationHandler];
}
// [END on_token_refresh]

@end

[〜#〜] update [〜#〜]
GCMメッセージを送信するためのバックエンドphpコード

//------------------------------
// Payload data you want to send 
// to Android device (will be
// accessible via intent extras)
//------------------------------
$msg = addslashes($_POST["msg"]);

//------------------------------
// The recipient registration IDs
// that will receive the Push
// (Should be stored in your DB)
// 
// Read about it here:
// http://developer.Android.com/google/gcm/
//------------------------------

//------------------------------
// Call our custom GCM function
//------------------------------

sendGoogleCloudMessage( $msg );
echo "send";

//------------------------------
// Define custom GCM function
//------------------------------

function sendGoogleCloudMessage( $msg )
{
    //------------------------------
    // Replace with real GCM API 
    // key from Google APIs Console
    // 
    // https://code.google.com/apis/console/
    //------------------------------

    $apiKey = 'abc';

    //------------------------------
    // Define URL to GCM endpoint
    //------------------------------

    $url = 'https://Android.googleapis.com/gcm/send';

    //------------------------------
    // Set CURL request headers
    // (Authentication and type)
    //------------------------------

    $headers = array( 
                        'Authorization: key=' . $apiKey,
                        'Content-Type: application/json'
                    );

    //------------------------------
    // Initialize curl handle
    //------------------------------

    $ch = curl_init();

    //------------------------------
    // Set URL to GCM endpoint
    //------------------------------

    curl_setopt( $ch, CURLOPT_URL, $url );

    //------------------------------
    // Set request method to POST
    //------------------------------

    curl_setopt( $ch, CURLOPT_POST, true );

    //------------------------------
    // Set our custom headers
    //------------------------------

    curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );

    //------------------------------
    // Get the response back as 
    // string instead of printing it
    //------------------------------

    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );

    //------------------------------
    // Set post data as JSON
    //------------------------------

    $post_json_encode = '{"data":{"message":"' . $msg . '"},"to":"/topics/global"}';

    curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_json_encode );

    //------------------------------
    // Actually send the Push!
    //------------------------------

    $result = curl_exec( $ch );

    //------------------------------
    // Error? Display it!
    //------------------------------

    if ( curl_errno( $ch ) )
    {
        echo 'GCM error: ' . curl_error( $ch );
    }

    //------------------------------
    // Close curl handle
    //------------------------------

    curl_close( $ch );

    //------------------------------
    // Debug GCM response
    //------------------------------
    $arr_result =   json_decode($result, true);
    foreach ($arr_result as $name => $value) {
        echo "<p>".$name .": ". $value ."</p>";
    }
}
15
setzuiro

2001年に発生するエラーは、kGGLInstanceIDOperationErrorCodeMissingKeyPairではなく、kGCMServiceErrorCodeAlreadyConnectedです。後者は、すでにGCMに接続していることを意味します。これをより適切にデバッグするために、表示通知をデバイストークンに送信しようとします。つまり、これを送信します。

$ post_json_encode = '{"notification":{"body": "'。$ msg。 '"}、 "to": "/ topics/global"}';

理論的には、アプリがフォアグラウンドにあるときにGCMに接続し、バックグラウンドに移動するときに切断する必要があります。その後、フォアグラウンドに到達したときに再接続できます。

data payloadおよびnotification payloadはiOSとAndroidの両方に適用できます。 iOSの違いは、通知ペイロードはAPNSを介して送信されるのに対し、データペイロードはアプリがフォアグラウンドにある場合にのみ存在するGCM自体の接続を介して送信されることです。 Android通知ペイロードは、最近追加された新しい表示通知のものです。

4
evanescent

私はiOSでも同じ問題に直面していました。それから私はPushBotsのウェブサイトで解決策を見つけました。今はうまくいっています。

XCodeで、[ターゲット]> [ビルド設定]> [コード署名ID]に移動し、自動ではないことを確認して、たとえばアプリケーションIDにリンクされたプロファイル一致証明書に設定します

0
Agnit