I am using GCM for app notifications as illustrated here. The code is boilerplate:
GCMRegistrar.checkDevice(this);
GCMRegistrar.checkManifest(this);
final String regId = GCMRegistrar.getRegistrationId(this);
if (regId.equals("")) {
GCMRegistrar.register(this, SENDER_ID);
} else {
Log.v(TAG, "Already registered");
}
Then the onRegistered() callback updates my server with the returned registration id.
However, on several occasions GCM has gotten into an inconsistent state. In one case, it doesn't forward any notifications even though GCMRegistrar.getRegistrationId() gives me a non-empty registration id string. In other cases, I end up getting two or more duplicate messages.
In attempt to avoid these situations, I currently have a brute force solution that works like this:
if (GCMRegistrar.isRegistered(this))
GCMRegistrar.unregister(this);
else
GCMRegistrar.register(this, GCMConstants.SENDER_ID);
In the onUnregistered() callback, I tell my server to delete the registration id, and then call GCMRegistrar.register(). In the onRegistered() callback, I tell my server to add the registration id back.
So far, it's working, but it's obviously less efficient than the way this is supposed to work, and I don't know for sure if it's immune to the inconsistency problems. It would be better if I could count on getRegistrationId() to always return a valid id, i.e., the current/canonical registration id.
What's the simplest/cleanest way to ensure consistency between my app, my server, and GCM? Thanks.
The problem of getting duplicate messages can be solved if you parse the response you get from Google, and identify the situation where you receive a canonical registration id in the response. In this case you can delete the old registration id, and keep the canonical one instead.
I think a better solution than your brute force solution is to always call register when the app is launched. You may receive in onRegistered() the same registration ID you got on the previous call, but if you persist that registration id in your device, you can compare the persisted ID to the received ID and avoid re-sending the registration ID to your server if it didn't change.
Related
I have a question about the correct use of GCM-IDs.
At the moment I have a InstanceIDListenerService a GcmListenerService and a RegestrationIntentService.
The RegeistrationIntentServie get started in the MainActivity every time someone opens the app.
I think this is a correct implementation of the Google guidelines.
But what is the best way to handle the GCM-ID so that I will not have incorrect GCM-IDs on my server after the refresh in the InstandeIDListenerService. Because at the moment the refresh only registers a new GCM-ID on my server.
Would it be an idea to generate a random Device ID so that I can update the old GCM-ID?
How do you handle the IDs?
Because at the moment I ask the server every start of the app if he knows a GCM-ID in combination with a (randomly generated) Device-ID and update one of both if the other one is incorrect or does not exist.
And I think that produces a lot of network traffic for nothing.
Just to be sure, you are not calling InstanceIDListenerService.onTokenRefresh() yourself right? This will be called by the system if necessary.
To answer your question, you should use the GCM functionality called "canonical IDs":
Canonical IDs
If a bug in the client app triggers multiple registrations for the
same device, it can be hard to reconcile state and the client app
might end up with duplicate messages.
Implementing canonical IDs can help you more easily recover from these
situations. A canonical registration ID is the registration token of
the last registration requested by the client app. This is the ID
that the server should use when sending messages to the device.
If you try to send a message using an old registration token, GCM will
process the request as usual, but it will include the canonical ID in
the registration_id field of the response. Make sure to replace the
registration token stored in your server with this canonical ID, as
eventually the old registration token will stop working.
To be more precise, if there are two registrations for the same device and you send a notification using the older registration ID, you will get the canonical ID (the registration ID of the newest registration for this device). If this ID is already stored on your server, delete the old registration. If the canonical ID is not stored on your server for any reason, replace the registration ID you used to send the notification with the canonical ID.
#leet GCM ID token initiates callback periodically when your token needs to be refresh, or sometime when:
- Security issues; like ssl or platform issues
- Device info is no longer valid; for example backup and restore.
- Instance ID service is otherwise affected.
public class MyInstanceIDService extends InstanceIDListenerService {
public void onTokenRefresh() {
refreshAllTokens();
}
private void refreshAllTokens() {
// assuming you have defined TokenList as
// some generalized store for your tokens
ArrayList<TokenList> tokenList = TokensList.get();
InstanceID iid = InstanceID.getInstance(this);
for(tokenItem : tokenList) {
tokenItem.token =
iid.getToken(tokenItem.authorizedEntity,tokenItem.scope,tokenItem.options);
// send this tokenItem.token to your server
}
}
};
Keeping the Registration State in
Sync
To protect the client app and app server from potential malicious
re-use of registration tokens, you should periodically initiate token
refresh from the server. When GCM registration token refresh is
initiated from server side, the client app must handle
a tokenRefreshed message with the GCM registration client/server
handshake. This link may help you verify on how
to handle Refresh Token.
As Baris have metioned, your server must use canonical IDs when you are sending message to the device. The tokenRefresh would remove the idea of generating a random device ID and Canonical IDs would solve on how you verify if the ID is correct or not.
I wanted a solution for the following problem:
I want to send notification specific to user like thank you message for one user and job posted say another user. I am looking for a way so that I can send separate messages depending on user id and registration id.
I tried the following method ie.
When a new user registers then, that users userid gets created on the database. I thought if I also create a registration id at the same time then I can save that in a separate table on the server. which will contain user id and registration_id and device type
But I noticed that at the time of GCM registration it is the Asynchronous task so I obtain it but till the user gets registered the id is still null. So it fails to get into the database.
If anyone has ideas on this particular problem. Or also has solved similar issue. Then do let me know.
Thanks for reading.
Actually the problem is GCM registration often reuires some time to register. So there is a simple way to get your id registered in your database. One is, if you are using splash screen in your app, then try to register for GCM there and in onRegistered method of your GCMIntentService save your registrationId in preferences like this
#Override
protected void onRegistered(Context arg0, String regId) {
Log.v("registrationId", regId);
prefs = getSharedPreferences("filename", 0);
prefs.edit().putString("regid", regId).commit();
}
Now at the time of login you should try to get your registrationId from the prefs where you have stored in onRegistered method. But still there is 1-2 % chance that you will not get you regId here. so for that, you can make a separate api for GCM Registration, that you will hit((if your regid is not registered at Login time)) in Activity next to your LoginActivity to get the id registered to your database.
Secondally, if you are not using splash screen, then you have to make separate api for GCM Registration because there is more chance that you will not get your registrationId during login.
I am seeing this issue on my push notifications server - different Android devices (identified by their IMEI) receive the SAME registration id from Google's GCM service.
Isn't the registration id supposed to be unique? at east for the same application or GCM API Key?
I saw this thread but there doesn't seem to be an answer there:
Can two different devices have same GCM Registration ID?
Any help would be much appreciated
EDIT
here is the relevant code for registration:
Intent intent = new Intent(
"com.google.android.c2dm.intent.REGISTER");
intent.setPackage("com.google.android.gsf");
intent.putExtra("app",
PendingIntent.getBroadcast(context, 0, new Intent(), 0));
intent.putExtra("sender", flatSenderIds);
context.startService(intent);
The only idea that comes to my mind is that this deprecated method will assign the same ID on different devices if the extra value for sender is the same on all of them. That differs from the current version where you don't say who you are, you don't even send an Intent, you just call register() and Google determines who you are and what ID you should be given.
If you finally use this new library, there's an example snippet of how to register (inside an AsyncTask or a Thread):
GoogleCloudMessaging gcm = null;
try {
// Will be for the first time
if (gcm == null)
gcm = GoogleCloudMessaging.getInstance(your_context);
// Registration against the GCM service
String regid = gcm.register(YOUR_SENDER_ID_OBTAINED_FROM_YOUR_PROJECT);
// You'll need to send the registration info to your remote server
registerRemoteServer(regid);
Log.d("MyGCM", "Registered on GCM as " + regid);
}
catch (final IOException ex) { ex.printStackTrace(); }
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 says:
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.
Well, after adding your registration code, I can see you are using the old method of registration. That method has been depecated since the middle of last year. It is likely to be less reliable than the new registration method.
You should try registering via the GoogleCloudMessaging.register method of the Google Play Services library. That's the recommended way by Google. See the official demo here.
Is there something wrong in the fact that every time I log in to my android application, the first thing I do is to register to GCM.
I know it's not necessary to do this, but in order to check if the registration id is refreshed to a new one or it s still the same, I plan to re-register.
final String regId = GCMRegistrar.getRegistrationId(this);
if (regId.equals("")) {
GCMRegistrar.register(this, SENDER_ID);
} else {
Log.v(TAG, "Already registered");
}
I would also like to know if by re-registering to GCM I am somehow forcing the registrationID to expire.
Thank you!!!
Part 1:
In order to check if the registration id is refreshed to a new one or it is still the same, I plan to re-register.
Now, if you see the second point under the heading Enable GCM on Architectural Overview page, it says:
Note that Google may periodically refresh the registration ID, so you should design your Android application with the understanding that the com.google.android.c2dm.intent.REGISTRATION intent may be called multiple times. Your Android application needs to be able to respond accordingly.
So, Google automatically sends you this broadcast, when it renews the ID. You might not necessarily send check the emptyness of the ID. When Google changes the ID, then also it won't be empty, right? So if you want to handle the renewal/refreshing of the ID then you may do the following:
You should have a Broadcast Listener which could handle com.google.android.c2dm.intent.REGISTRATION intent, which Google send to the app when it has to refresh the registration ID. The broadcast receiver will have the onReceive method with an Intent. From the intent you can get the Bundle using which you can extract the new registration ID from Google. You can save that and send it to the 3rd part server to replace your previous registered ID for that user.
Also you may see this answer on the question In GoogleCloudMessaging API, how to handle the renewal or expiration of registration ID?.
Part 2:
Answering your second part of question:
You may want to read Unregistration here. The docs says If you unregister and then re-register, GCM may return the same ID or a different ID—there's no guarantee either way.
I think that whenever an ID will be about to expire/renew, Google will send you the new ID. Then the response from Google will contain a Canonical Registration ID (which is the new registration ID). This response indicates that your server should delete the old registration ID and use only the new one. ( Source : Answer at Unregistering and re-registering for GCM messages causes two regId's to be valid. Is this as intended? question by #Eran)
Hope this helps you understand how to handle it.
I'm developing a small application using GCM Service.
Before, I tried to send to my self a message, but (server side) the answer has been:
"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"NotRegistered"}]}
But at the beggining of my app i check this:
final String regId = GCMRegistrar.getRegistrationId(this); if (regId.equals(""))
{
// Register
GCMRegistrar.register(this, SENDER_ID);
}
else ...
It seems evident that getRegistrationId() works fine only locally, (SharedPreferences ?)..
The nice thing is that i never did Unregister my app,just reinstall it, not changing version number (because it is in test,still) so in this case my account has expired, in those cases google should not send me another id that i could catch here:
#Override
protected void onRegistered
???
However there is a safe way to understand if my app is registered GCM server side?
Thanks!
EDIT:
I'm thinking this:
When i reinstall my application through Eclipse, there is a moment where my application is not installed, if GCM server send me a message in that moment there is no receiver and so google unvalidates my ID.
In your opinion is this idea, a stupid idea?
follow these steps http://developer.android.com/google/gcm/gs.html.
It could happen because some time gcm registartion id gets expired so everytime you should check that is it registered or not if not register it
You should only register the device one time. Save the registrationId in some way (shared preferences is a good one) and use it.
From the documentation:
Register the application for GCM and return the registration ID. You must call this once, when your application is installed, and send the returned registration ID to the server.
In some tests i've made, the registration id returned by gcm.register(id) isn't always the same for the device,app pair. However, Google says:
Repeated calls to this method will return the original registration ID.
If the method gcm.register(id) returns an id, it will be valid ever since.
Best.