Android GCM and multiple tokens

2020-01-29 14:12发布

问题:

I register in GCM with GoogleCloudMessaging.getInstance(context); and save received token on device. Then send it to server and it's associated with user account. If I uninstall my app without logging out and install again and log in with another user, I receive new token and send it to server. And when pushes being send to first user I see them when I logged in with second user.

Why does GCM sends me different tokens and how can I handle this?

回答1:

Welcome to the marvelous world of duplicate messages from Google Cloud Messaging. When this happens, the GCM engine enables the Canonical IDs to solve it. This might happen because you registered with several IDs for the same device, or because the GCM server didn't get the unregister() call when the app was uninstalled. Using canonical IDs will set your ID to be the last registration you've made.

As per the GCM reference about this:

Canonical IDs

On the server side, as long as the application is behaving well, everything should work normally. However, if a bug in the application triggers multiple registrations for the same device, it can be hard to reconcile state and you might end up with duplicate messages.

GCM provides a facility called "canonical registration IDs" to easily recover from these situations. A canonical registration ID is defined to be the ID of the last registration requested by your application. This is the ID that the server should use when sending messages to the device.

If later on you try to send a message using a different registration ID, GCM will process the request as usual, but it will include the canonical registration ID in the registration_id field of the response. Make sure to replace the registration ID stored in your server with this canonical ID, as eventually the ID you're using will stop working.

More info here.

Also there is a practical case on how to procceed, it might be helpful:

  • Canonical Registration ID and message ID format


回答2:

I experienced registration ID changes when uninstalling the application, attempting to send messages to the app while it's uninstalled (until I get a NotRegistered error) and then installing again.

Costin Manolache from Google suggests to handle registration ID changes this way :

The suggestion/workaround is to generate your own random identifier, saved as a shared preference for example. On each app upgrade you can upload the identifier and the potentially new registration ID. This may also help tracking and debugging the upgrade and registration changes on server side.

Of course, this only works when the app remains installed (since shared preferences are deleted with the app). However, if the device has external storage, you can store your identifier there, and when the app is installed again, load the stored identifier from the external storage. That way you will know the new Registration ID and the old Registration ID belong to the same device.

In addition, you should handle canonical registration ID responses from Google in your server, as mentioned in the other answer.



回答3:

You can send Android device id along with registration id. Android device id is unique and remains the same during app uninstall reinstall and only changes if device is factory reset.

Example: How to get unique device hardware id in Android?



回答4:

Sending Push Notification to multiple devices is same as we send to individual device. Just store the registration token of all registered device to your server. And when calling the push notification with curl (I am assuming you are using php as server side) Put all the registration id in an array. This is a sample code

<?php
//Define your GCM server key here 
define('API_ACCESS_KEY', 'your server api key');

//Function to send push notification to all 
function sendToAll($message)
{
    $db = new DbOperation();
    $tokens = $db->getAllToken();
    $regTokens = array();
    while($row = $tokens->fetch_assoc()){
        array_push($regTokens,$row['token']);
    }
    sendNotification($regTokens,$message);
}


//function to send push notification to an individual 
function sendToOne($email,$message){
    $db = new DbOperation();
    $token = $db->getIndividualToken($email);
    sendNotification(array($token),$message);
}


//This function will actually send the notification
function sendNotification($registrationIds, $message)
{
    $msg = array
    (
        'message' => $message,
        'title' => 'Android Push Notification using Google Cloud Messaging',
        'subtitle' => 'www.simplifiedcoding.net',
        'tickerText' => 'Ticker text here...Ticker text here...Ticker text here',
        'vibrate' => 1,
        'sound' => 1,
        'largeIcon' => 'large_icon',
        'smallIcon' => 'small_icon'
    );

    $fields = array
    (
        'registration_ids' => $registrationIds,
        'data' => $msg
    );

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

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://android.googleapis.com/gcm/send');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
    $result = curl_exec($ch);
    curl_close($ch);

    $res = json_decode($result);

    $flag = $res->success;
    if($flag >= 1){
        header('Location: index.php?success');
    }else{
        header('Location: index.php?failure');
    }
}

In the above code we are fetching the registration token from the mysql table. To send to all devices we need all tokens. And to send an individual device we need token for that device only.

Source: Google Cloud Messaging Example



回答5:

first when you send notification send user id with it and ask if id in sharedpreference == comming or no

if you send notifications to all users and may be some one get 2 notifications while he should only get one do that

Create file on your server and with any number say 0 then when you want to send notification send this numebr with it then add one to this number++ to be new number in the next notification same with every new one

In android application add variable and let this variable = the comming variable from the server after add the notification but you need to ask if(number_in_your_service!=server_number) //add notification

any number of notifications you send just one will appear

public class GcmIntentService extends IntentService {
  public static int openintent;
  public static final int NOTIFICATION_ID = 1;
  private static final String TAG = "GcmIntentService";

  private static String number_in_your_service="somethingneversend";
  NotificationCompat.Builder builder;

  public GcmIntentService() {
    super("GcmIntentService");
  }

  @Override
  protected void onHandleIntent(Intent intent) {
    Bundle extras = intent.getExtras();
    GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
    String messageType = gcm.getMessageType(intent);

    if (!extras.isEmpty()) { // has effect of unparcelling Bundle
      if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
      } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {
        // If it's a regular GCM message, do some work.
      } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
        // This loop represents the service doing some work.
        for (int i = 0; i < 5; i++) {
          Log.i(TAG, "Working... " + (i + 1) + "/5 @ " + SystemClock.elapsedRealtime());
          try {
            Thread.sleep(100);
          } catch (InterruptedException e) {
          }
        }
        Log.i(TAG, "Completed work @ " + SystemClock.elapsedRealtime());
        // Post notification of received message.
        sendNotification(extras);
        Log.i(TAG, "Received: " + extras.toString());
      }
    }
    // Release the wake lock provided by the WakefulBroadcastReceiver.
    GcmBroadcastReceiver.completeWakefulIntent(intent);
  }

  private void sendNotification(Bundle extras) {

    if((extras.getString("server_number")).equals(number_in_your_service)) {

      Intent intent = new Intent(this, Main_Page_G.class);
      intent.putExtra("frame",100);
      intent.putExtra("bundle",extras);
      final PendingIntent contentIntent = PendingIntent.getActivity(this,
            120, intent, PendingIntent.FLAG_UPDATE_CURRENT);
      NotificationManager mNotificationManager;
      NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
            GcmIntentService.this).setContentTitle("name")
            .setContentText("content")
            .setDefaults(Notification.DEFAULT_SOUND)
            .setContentInfo("Test")
            .setSmallIcon(R.drawable.rehablogo2)
            .setAutoCancel(true);
      mBuilder.setContentIntent(contentIntent);
      mNotificationManager = (NotificationManager) GcmIntentService.this
            .getSystemService(Context.NOTIFICATION_SERVICE);
      mNotificationManager.notify(id, mBuilder.build());
      id=Integer.parseInt(extras.getString("id"));
    }
  }
}