iravul
iravul

Reputation: 408

Android GCM one device has multiple registrationid

I have system that has PHP Server side and Android Client side application. Android sends parameters via webservice and PHP handle the GCM side. PHP sends push notification and before sent, it gets all registrationid from DB. The problem is, same device may have two or more registrationid. Because of this, push notification sent to same devices for two or more times. Is there any solution to handle this problem?

Upvotes: 5

Views: 7550

Answers (8)

dis.iz.peez
dis.iz.peez

Reputation: 89

Google's sample code that comes with the backend that gets generated from the template in Android Studio does this for you. Check out the MessageEndpoint class and you'll notice that They are checking for a canonical id and if it exists then the assumption is that the regid has changed and therefore ideally needs to be updated for that particular device.

public void sendMessage(@Named("message") String message) throws IOException {
    if (message == null || message.trim().length() == 0) {
        log.warning("Not sending message because it is empty");
        return;
    }
    // crop longer messages
    if (message.length() > 1000) {
        message = message.substring(0, 1000) + "[...]";
    }
    Sender sender = new Sender(API_KEY);
    Message msg = new Message.Builder().addData("message", message).build();
    List<RegistrationRecord> records = ofy().load().type(RegistrationRecord.class).limit(10).list();
    for (RegistrationRecord record : records) {
        Result result = sender.send(msg, record.getRegId(), 5);
        if (result.getMessageId() != null) {
            log.info("Message sent to " + record.getRegId());
            String canonicalRegId = result.getCanonicalRegistrationId();
            if (canonicalRegId != null) {
                // if the regId changed, we have to update the datastore
                log.info("Registration Id changed for " + record.getRegId() + " updating to " + canonicalRegId);
                record.setRegId(canonicalRegId);
                ofy().save().entity(record).now();
            }
        } else {
            String error = result.getErrorCodeName();
            if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
                log.warning("Registration Id " + record.getRegId() + " no longer registered with GCM, removing from datastore");
                // if the device is no longer registered with Gcm, remove it from the datastore
                ofy().delete().entity(record).now();
            } else {
                log.warning("Error when sending message : " + error);
            }
        }
    }
}

Upvotes: 0

Raghu
Raghu

Reputation: 31

Finally got working Solution for Duplicate Registration Id . Its all about updating existing registration id with canonical ids.

Upvotes: 0

Wasif Khalil
Wasif Khalil

Reputation: 2247

here is my solution to this problem. when you send a gcm notification you get a response like this

Array
    (
        [multicast_id] => 12345678910
        [success] => 8
        [failure] => 3
        [canonical_ids] => 4
        [results] => Array
            (
                [0] => Array
                    (
                        [error] => NotRegistered
                    )

                [1] => Array
                    (
                        [message_id] => 0:1412242641156904%3dc89e2df9fd7ecd
                    )

                [2] => Array
                    (
                        [registration_id] => APA91bH3WdWwqFCVKbvZXsf-gj28iWU5oYbSCRZYFp987CHasxwT_HOiE7dp8212XID0FMGVG2n4NLohFsEGYJ-LEA07xsgsKfT00xModQcx5QgTBmJtxlWgeaWFpz29z-iCPYbvOHEhqfwHmN-HIps7DiWntcs-Qg
                        [message_id] => 0:1412242641155639%3dc89e2df9fd7ecd
                    )

                [3] => Array
                    (
                        [registration_id] => APA91bH3WdWwqFCVKbvZXsf-gj28iWU5oYbSCRZYFp987CHasxwT_HOiE7dp8212XID0FMGVG2n4NLohFsEGYJ-LEA07xsgsKfT00xModQcx5QgTBmJtxlWgeaWFpz29z-iCPYbvOHEhqfwHmN-HIps7DiWntcs-Qg
                        [message_id] => 0:1412242641155809%3dc89e2df9fd7ecd
                    )


                [4] => Array
                    (
                        [message_id] => 0:1412242641157971%3dc89e2df9fd7ecd
                    )


                [5] => Array
                    (
                        [registration_id] => APA91bGXo_gnfBZsvPoqJTYy1BWz0FQIkwlD1EmBtcxgWWfceYvd0ehWqVCtfd8n56VGYrvXCS2v48kTiA69BD7Sci0BA9a9bKTIg_MUEnDd79ssCK-miPG88DDGL4oKtB14cPbh-_xbgVRZllMOzwNZf_w5uJGR8g
                        [message_id] => 0:1412242641157969%3dc89e2df9fd7ecd
                    )


                [6] => Array
                    (
                        [registration_id] => APA91bGXo_gnfBZsvPoqJTYy1BWz0FQIkwlD1EmBtcxgWWfceYvd0ehWqVCtfd8n56VGYrvXCS2v48kTiA69BD7Sci0BA9a9bKTIg_MUEnDd79ssCK-miPG88DDGL4oKtB14cPbh-_xbgVRZllMOzwNZf_w5uJGR8g
                        [message_id] => 0:1412242641157967%3dc89e2df9fd7ecd
                    )

            )

    )

in the array there is an index canonical_ids which is 4 that means u have 4 users have generated new gcm ids, now all you need to do is replace their gcmids with the new one you received in the above array, next time when you send gcm notification.

my php app is built on codeigniter framework so here is what i did. i have a model called login and in there i have a function that returns gcmids from database along with the userid of the user as an index of the array

function getAllRegIds(){
    $query = "SELECT gcmid,id FROM users_app WHERE gcmid IS NOT NULL AND gcmid != '' GROUP BY gcmid"; //group by incase a user loggedin with multiple userids in your a
    $query = $this->db->query($query);

    if($query->num_rows() > 0){
        $result = $query->result_array();
        $regids = array();

        foreach($result as $row){
            $regids[$row['id']] = $row['gcmid'];
        }
        return $regids; 
    }else{
        return false;
    }
}

the array looks like this

Array(
  [29] => APA91bEYda3DVb...
  [1] => APA91bF0yfdZjX4...
  [12] => APA91bG-9fsBGT-...
  [11] => APA91bGNRh_VWF...
  [3] => APA91bGXo_gnfBZ...
  [2] => APA91bH3WdWwqFC...
  [26] => APA91bHn8Ufwe4...
)

index is the userid with value gcmid

this function is called inside controller function, here is the implementation

public function sendtoall(){
    $this->load->model('app/login');

    $result = $this->login->getAllRegIds(); //here i got all gcmids with userids as index

    $message = $this->input->post('message');
    $chunks = array_chunk($result,1000); //broken the array into chunks of 1000 ids because gcm can only send message to 1000 ids at a time

    $status = $this->sendNotification($chunks,$message);


    if(!empty($status) && is_array($status)){
        foreach($status as $key=>$row){

            if(array_key_exists('canonical_ids',$row) && $row['canonical_ids'] > 0){
                $canonical_ids = $row['canonical_ids'];

                if(array_key_exists('results',$row) && !empty($row['results']) && is_array($row['results'])){
                    foreach($row['results'] as $k=>$v){
                        if(array_key_exists('registration_id',$v)){

                            $userid = array_search($chunks[$key][$k], $result);
                            $newgcmid  = $v['registration_id'];

                            $this->login->updateGCMId($userid,$newgcmid);
                        }
                    }
                }
            }
        }
    }

    echo json_encode($status);
}

as you in the comments i have broken the array into chunks of 1000 ids because gcm can send message to 1000ids at a time, and called a function sendNotification that return the response received by gcm, which is the array i pasted above that has all the canonical ids.

then i checked if the status is not empty and is an array, foreach the status because it may have multiple response arrays because i am sending notification in chunks of 1000 ids , see the sendNotification implementation below

then i checked if it has a index canonical_ids and is greater than 0, which means there are ids that needs to be replaced, then i checked if it has the index result, not empty and is an array.

foreach result and get the keys and values of those index that has registration_id index in it, which means these ids needs to be replaced in our db

get the userid of the user whose gcmid needs to be replaced by searching the result array, with the oldgcm id of the user($chunks[$key][$k], the first parameter is the key of the chunk that has the gcmid you need and second is the index of that gcmid)

get the new gcmid from the results array

call the model function updateGCMId, pass the userid and newgcmid.

here is the implementation of sendNotification and updateGCMId functions.

private function sendNotification($regids,$message){
    $status = array();
    $result = $regids;
    if($result){
        foreach($result as $thousandids){
            $registatoin_ids=$thousandids;

            $msg=array("message"=>$message);



            $url='https://android.googleapis.com/gcm/send';
            $fields=array
             (
              'registration_ids'=>$registatoin_ids,
              'data'=>$msg
             );
            $headers=array
             (
              'Authorization: key=AIza..............',
              'Content-Type: application/json'
             );
            $ch=curl_init();
            curl_setopt($ch,CURLOPT_URL,$url);
            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);


            $result = json_decode($result,1);

            $status[] = $result;

        }
    }

    return $status;
}

update function:

public function updateGCMId($userid,$gcmid){
      $query = 'UPDATE users_app SET 
                  gcmid = "'.$gcmid.'" 
                WHERE id = "'.$userid.'"';
      $this->db->query($query);
}

Upvotes: 2

techrahul87
techrahul87

Reputation: 107

You will get canonical Ids in your push response. You need to update your Old Id with new canonical Ids.

There is one more solution. More like a patch. You can pass parameter 'dry_run' in GCM request JSON. When we set it to true, it will send a fake message to device Id and generate a response.

Device will not get a message but you will get your response, hence you can check which device Ids have registration_ids in their result and remove them from your database.

        $fields = array(
                    'registration_ids'  => $deviceId,
                    'data' => array( "message" =>'fake_message'),
                    'dry_run'=>true
                    );

Hope it helps.

Upvotes: 0

Sash_KP
Sash_KP

Reputation: 5591

Sometimes Google changes the registration ID and you'll have multiple IDs associated. The server that sends the notification (your server) has to update the database with the new ID.

For more info check this document:

http://developer.android.com/google/gcm/adv.html

they say:

It's an 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.

Upvotes: 3

shemsu
shemsu

Reputation: 1076

You just have to store a uniq id for each device in your database. Then, you can send just one push notification for every devices.

I've never see multiple registration id for the same device. Strange thing.

Did you manage the registration id update correctly ?

Upvotes: 2

AlexBcn
AlexBcn

Reputation: 2460

GCM can change the registrationId and you have to update it on your server-side.

  • When you send a message the result can contain the new registrationId that is going to be used for the device, so you have to update the old registrationId for the new one.

Look this link at Interpreting a success response section

If message_id is set, check for registration_id: If registration_id is set, replace the original ID with the new value (canonical ID) in your server database. Note that the original ID is not part of the result, so you need to obtain it from the list of registration_ids passed in the request (using the same index).

Upvotes: 4

user1432059
user1432059

Reputation:

I think your problem is how you get the registration id from the device. You must be sure that it sends one time the regID and when the GCM server request a refresh, you must resend the regID to your webserver and store it, overwriting the old redID. See this part of android reference.

Upvotes: 0

Related Questions