Home » Android » android – GCM: MulticastResult – which result is from which device?

android – GCM: MulticastResult – which result is from which device?

Posted by: admin April 23, 2020 Leave a comment

Questions:

Following the last section in the GCM: Getting Started guide, there’s some book-keeping to be done after receiving the results.

Quoting from the guide:

It’s now necessary to parse the result and take the proper action in the following cases:

  • If the message was created but the result returned a canonical registration ID, it’s necessary to replace the current registration
    ID with the canonical one.
  • If the returned error is NotRegistered, it’s necessary to remove that registration ID, because the application was uninstalled from
    the device.

Here’s a code snippet that handles these 2 conditions:

if (result.getMessageId() != null) {
 String canonicalRegId = result.getCanonicalRegistrationId();
 if (canonicalRegId != null) {
   // same device has more than on registration ID: update database
 }
} else {
 String error = result.getErrorCodeName();
 if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
   // application has been removed from device - unregister database
 }
}

The guide above refers to a single result, and not to the multicast case.
I’m not sure how to handle the multicast case:

    ArrayList<String> devices = new ArrayList<String>();

    for (String d : relevantDevices) {
        devices.add(d);
    }

    Sender sender = new Sender(myApiKey);
    Message message = new Message.Builder().addData("hello", "world").build();
    try {
        MulticastResult result = sender.send(message, devices, 5);

        for (Result r : result.getResults()) {
            if (r.getMessageId() != null) {
                String canonicalRegId = r.getCanonicalRegistrationId();
                if (canonicalRegId != null) {
                    // same device has more than on registration ID: update database
                    // BUT WHICH DEVICE IS IT?
                }
            } else {
                String error = r.getErrorCodeName();
                if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
                    // application has been removed from device - unregister database
                    // BUT WHICH DEVICE IS IT?
                }
            }
        }
    } catch (IOException ex) {
        Log.err(TAG, "sending message failed", ex);
    }

I submit a list of devices, and receive back a list of results.
The Result object doesn’t contain the registration id, but only a canonical id if the first is obsolete.
It is undocumented if the two lists are co-related (ie. preserves order and size).

How can I be sure which result refer to which device?

— UPDATE

I’ve pasted a snippet of the solution in a separate answer below

How to&Answers:

The results are in the order of your registration_id array that you sent to GCM server. e.g. if your registration_ids are:

[id1, id4, id7, id8]

Then the results array you get will have same order for id1, id4, id7, and id8.

You just need to parse each result accordingly, e.g. if the 2nd result has ‘message_id’ and ‘registration_id’ of ‘id9’, you know ‘id4’ is now obsolete and should be replaced by id9.

Answer:

For the readers convenience, here is a snippet that handles response for multiple devices

public void sendMessageToMultipleDevices(String key, String value, ArrayList<String> devices) {

        Sender sender = new Sender(myApiKey);
        Message message = new Message.Builder().addData(key, value).build();
        try {
            MulticastResult result = sender.send(message, devices, 5);
            MTLog.info(TAG, "result " + result.toString());


            for (int i = 0; i < result.getTotal(); i++) {
                Result r = result.getResults().get(i);

                if (r.getMessageId() != null) {
                    String canonicalRegId = r.getCanonicalRegistrationId();
                    if (canonicalRegId != null) {
                        // devices.get(i) has more than on registration ID: update database

                    }
                } else {
                    String error = r.getErrorCodeName();
                    if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
                        // application has been removed from devices.get(i) - unregister database
                    }
                }
            }
        } catch (IOException ex) {
            MTLog.err(TAG, "sending message failed", ex);
        }
    }

Answer:

This solution is done by google developer sample GCM Demo application
note the asyncSend for multicasting handle

List<GcmUsers> devices=SearchRegisterdDevicesByCourseCommand.execute(instructorId, courseId);
    String status;
    if ( devices.equals(Collections.<GcmUsers>emptyList())) {    
      status = "Message ignored as there is no device registered!";
    } else {
      // NOTE: check below is for demonstration purposes; a real application
      // could always send a multicast, even for just one recipient
      if (devices.size() == 1) {
        // send a single message using plain post
        GcmUsers gcmUsers = devices.get(0);
        Message message = new Message.Builder().build();
        Result result = sender.send(message, gcmUsers.getGcmRegid(), 5);
        status = "Sent message to one device: " + result;
      } else {
        // send a multicast message using JSON
        // must split in chunks of 1000 devices (GCM limit)
        int total = devices.size();
        List<String> partialDevices = new ArrayList<String>(total);
        int counter = 0;
        int tasks = 0;
        for (GcmUsers device : devices) {
          counter++;
          partialDevices.add(device.getGcmRegid());
          int partialSize = partialDevices.size();
          if (partialSize == MULTICAST_SIZE || counter == total) {
            asyncSend(partialDevices);
            partialDevices.clear();
            tasks++;
          }
        }
        status = "Asynchronously sending " + tasks + " multicast messages to " +
            total + " devices";
      }
    }
    req.setAttribute(HomeServlet.ATTRIBUTE_STATUS, status.toString());






private void asyncSend(List<String> partialDevices) {
    // make a copy
    final List<String> devices = new ArrayList<String>(partialDevices);
    threadPool.execute(new Runnable() {

      public void run() {
        Message message = new Message.Builder().build();
        MulticastResult multicastResult;
        try {
          multicastResult = sender.send(message, devices, 5);
        } catch (IOException e) {
          logger.log(Level.SEVERE, "Error posting messages", e);
          return;
        }
        List<Result> results = multicastResult.getResults();
        // analyze the results
        for (int i = 0; i < devices.size(); i++) {
          String regId = devices.get(i);
          Result result = results.get(i);
          String messageId = result.getMessageId();
          if (messageId != null) {
            logger.fine("Succesfully sent message to device: " + regId +
                "; messageId = " + messageId);
            String canonicalRegId = result.getCanonicalRegistrationId();
            if (canonicalRegId != null) {
              // same device has more than on registration id: update it
              logger.info("canonicalRegId " + canonicalRegId);
              Datastore.updateRegistration(regId, canonicalRegId);
            }
          } else {
            String error = result.getErrorCodeName();
            if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
              // application has been removed from device - unregister it
              logger.info("Unregistered device: " + regId);
              Datastore.unregister(regId);
            } else {
              logger.severe("Error sending message to " + regId + ": " + error);
            }
          }
        }
      }});
  }