I am implementing Google Cloud Messaging service in my cordova app. so far everything is working well. I however have some few issues bothering I wish someone can clarify them for me.
1) At this section of the code where I get the device GCM regID and further save it to on my server. I will like to know if I should call this script and thus save GCM regID to my server anytime the user opens the App or it should be called and saved once..
function onNotification(e) {
switch( e.event )
{
case 'registered':
if ( e.regid.length > 0 )
{
console.log("regID = " + e.regid);
}
break;
}
}
2) I have noticed on the console that my registered ID sometime changes. I will like to know why that happens, whether it is normal and also if I should be updating the users GCM regID on my server.
I wil be glad if anyone can clarify these for me. Thank you
for (1) it should be called and saved on server once. as am working in my applications and save this id to server once, and all things working fine.
for (2) however in development the registrations id's sometimes changes. The reason is that in development we uninstall or reinstall the application completely. thus makes the registration id's to change.because registration id assigned on app installation. but for exceptional case also see this.
You should call the script every time. Only update the registration ID if it has changed.
Read my answer. Also, this answer for more details
You only need to send your registration token to your server once. If your registration token changes you should send the changed registration token to your server.
The registration token may change if the application is uninstalled and reinstalled or if GCM determines that the token has been compromised in some way.
You should register a service to listen for registration token changes it should extend InstanceIDListenerService and you override its onTokenRefresh method like this:
#Override
public void onTokenRefresh() {
// Fetch updated Instance ID token and notify our app's server of any changes (if applicable).
InstanceID instanceID = InstanceID.getInstance(this);
String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId),
GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
Log.i(TAG, "GCM Registration Token: " + token);
// Send token to server.
}
Consider GCM getting started documentation for more details.
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 am using Google's GCM service in my app. I tried the sample code and it worked fine for me. But there is one thing regarding the registration token that confuses me.
The sample code inside the function onHandleIntent(Intent intent) in RegistrationIntentService.java has lines to get the token and then uses is to subscribe the topic
InstanceID instanceID = InstanceID.getInstance(this);
String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId),
GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
I understand that the token refresh may be initiated by the system from reading the sample code MyInstanceIDListenerService.java, and then I will have to notify the server from the callback function onTokenRefresh().
I found that the function onHandleIntent(Intent intent) is always called when I tap the notification to open the app, as a result, the registration token will be generated again. My question is, I can set flat at the point when onTokenRefresh() to determine if I need to update the server and re-subscribe the topic. But can I safely assume the token will never get changed from app launch?
"Does GCM registeration token remain unchanged if app never update and the InstanceID provider never initiate refresh"
The short answer is yes. It never changes. If it does on token refresh will be called. As for the google sample code, there are few vital pieces missing from it which you have to fill up yourself. The first is that RegistrationIntentService does not check if the device is already registered. You should save this information in shared preferences.
If the device is already registered there is no need for this bit of code:
InstanceID instanceID = InstanceID.getInstance(this);
String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId),
GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
Now the question arises what happens if you do call execute this code over and over again? I did experiment with that and found that some devices kept giving the same token over and over again but other devices returned different tokens.
In the docs on Google Cloud Messaging, it states:
The Android application should store this ID for later use (for
instance, to check on onCreate() if it is already registered). 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.
I register my device using the following code:
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
String regID = gcm.register(senderID);
The GoogleCloudMessaging class encapsulates the registration process. So how am I suppose to handle com.google.android.c2dm.intent.REGISTRATION since handling that is done internally by the GoogleCloudMessaging class?
That's an interesting question.
Google encourage you to switch to the new registration process :
An Android application running on a mobile device registers to receive messages by calling the GoogleCloudMessaging method register(senderID...). This method registers the application for GCM and returns the registration ID. This streamlined approach replaces the previous GCM registration process.
The note that says Google may periodically refresh the registration ID only appears on the page that still shows the old registration process, so it's possible that this note is no longer relevant.
If you want to be safe, you can still use the old registration process. Or you can use the new process, but have in addition the code that handles the com.google.android.c2dm.intent.REGISTRATION intent, in order to make sure you are covered if Google do decide to refresh the registration ID.
That said, I never experienced such a refresh, and even when I did experience a change in the registration ID (usually as a result of sending a notification after un-installing the app and then re-installing it), the old registration ID still worked (resulting in a canonical registration ID sent in the response from Google), so no harm was done.
EDIT (06.06.2013) :
Google changed their Demo App to use the new interface. They refresh the registration ID by setting an expiration date on the value persisted locally by the app. When the app starts, they load their locally stored registration id. If it is "expired" (which in the demo means it was received from GCM over 7 days ago), they call gcm.register(senderID) again.
This doesn't handle the hypothetical scenario in which a registration ID is refreshed by Google for an app that hasn't been launched for a long time. In that case, the app won't be aware of the change, and neither will the 3rd party server.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mDisplay = (TextView) findViewById(R.id.display);
context = getApplicationContext();
regid = getRegistrationId(context);
if (regid.length() == 0) {
registerBackground();
}
gcm = GoogleCloudMessaging.getInstance(this);
}
/**
* Gets the current registration id for application on GCM service.
* <p>
* If result is empty, the registration has failed.
*
* #return registration id, or empty string if the registration is not
* complete.
*/
private String getRegistrationId(Context context) {
final SharedPreferences prefs = getGCMPreferences(context);
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
if (registrationId.length() == 0) {
Log.v(TAG, "Registration not found.");
return "";
}
// check if app was updated; if so, it must clear registration id to
// avoid a race condition if GCM sends a message
int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersion(context);
if (registeredVersion != currentVersion || isRegistrationExpired()) {
Log.v(TAG, "App version changed or registration expired.");
return "";
}
return registrationId;
}
/**
* Checks if the registration has expired.
*
* <p>To avoid the scenario where the device sends the registration to the
* server but the server loses it, the app developer may choose to re-register
* after REGISTRATION_EXPIRY_TIME_MS.
*
* #return true if the registration has expired.
*/
private boolean isRegistrationExpired() {
final SharedPreferences prefs = getGCMPreferences(context);
// checks if the information is not stale
long expirationTime =
prefs.getLong(PROPERTY_ON_SERVER_EXPIRATION_TIME, -1);
return System.currentTimeMillis() > expirationTime;
}
EDIT (08.14.2013) :
Google changed their Demo App again (two days ago). This time they removed the logic that considers the Registration ID to be expired after 7 days. Now they only refresh the Registration ID when a new version of the app it installed.
EDIT (04.24.2014) :
For the sake of completeness, here are the words of Costin Manolache (taken from here), a Google developer involved in the development of GCM, on the matter :
The 'periodical' refresh never happened, and the registration refresh
is not included in the new GCM library.
The only known cause for registration ID change is the old bug of apps
getting unregistered automatically if they receive a message while
getting upgraded. Until this bug is fixed apps still need to call
register() after upgrade, and so far the registration ID may change in
this case. Calling unregister() explicitly usually changes the
registration ID too.
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.
This explains the current implementation of the official GCM Demo application.
com.google.android.c2dm.intent.REGISTRATION should never be handled when using the GoogleCloudMessaging class to register.
Reading the new InstanceID API, I found more info on when the token might change:
Your app can request tokens from the Instance ID service as needed
using the getToken() method, and like InstanceID, your app can also
store tokens on your own server. All tokens issued to your app belong
to the app's InstanceID.
Tokens are unique and secure, but your app or the Instance ID service
may need to refresh tokens in the event of a security issue or when a
user uninstalls and reinstalls your app during device restoration.
Your app must implement a listener to respond to token refresh
requests from the Instance ID service.
More details:
The Instance ID service initiates callbacks periodically (for example,
every 6 months), requesting that your app refreshes its tokens. It may
also initiate callbacks when:
There are security issues; for example, SSL or platform issues.
Device information is no longer valid; for example, backup and restore.
The Instance ID service is otherwise affected.
Sources:
https://developers.google.com/instance-id/
https://developers.google.com/instance-id/guides/android-implementation
After scrubbing through tonnes of misleading answers across the net, including SO, the only place I found a complete answer was as remarked by Eran's answer and here:
While automatic registration refresh might or might never have happened, google describes a simiple algorithm to handle the canocical_ids by parsing successful response:
If the value of failure and canonical_ids is 0, it's not necessary to parse the remainder of the response. Otherwise, we recommend that you iterate through the results field and do the following for each object in that list:
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 code>registration_ids passed in the request (using the same index).
Otherwise, get the value of error:
If it is Unavailable, you could retry to send it in another request.
If it is NotRegistered, you should remove the registration ID from your server database because the application was uninstalled from the device or it does not have a broadcast receiver configured to receive com.google.android.c2dm.intent.RECEIVE intents.
Otherwise, there is something wrong in the registration ID passed in the request; it is probably a non-recoverable error that will also require removing the registration from the server database. See Interpreting an error response for all possible error values.
From aforementioned link.
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.
I am trying to migrate to GCM in Android, C2DM now being deprecated. The registration process described here is different from registration described here. Are both registration same? Can we see code for GCMRegistrar to know for sure?
I've successfully migrated my C2DM project to GCM. Tested, it works fine. The only changes were:
in the Android app - change the value of sender upon registration
on the server side - change the auth header and the URL
That was it, as far as the interaction with Google goes. There were more some changes dictated by the app's logic:
in the Android app, the registration ID was cached in its preferences. Upon upgrade, I remove reg ID from the preferences to force re-registration, this time with GCM.
the logic of passing the reg ID to the server got an extra boolean parameter - if this is a C2DM or GCM reg ID
the logic of sending messages became conditional upon the said parameter.
Throwing out the C2DM logic completely out of the server would be unwise - not everyone upgrades their Android apps. The old, C2DM-enabled versions will be out in the wild for some time. And Google pledged to keep C2DM running in the short term. So message sending is conditional - depending on reg ID type, it sends either to GCM or to C2DM.
EDIT re: conditional logic:
if($RegID_Is_GCM)
{
$Auth = GCM_Auth();
$URL = $GCM_URL;
}
else
{
$Auth = C2DM_AUTH();
$URL = $C2DM_URL;
}
They are actually the same thing. The second one encapsulates the first one in a static method and registers a broadcast receiver. You can attach the source to the gcm.jar and see for yourself. You can find source code in ~/android-sdks/extras/google/gcm/gcm-client/gcm-src.jar
The Thing I like most in GCM is the RegID we will get from GCM server,it is not only an ID its an Address of this application on this Device. So this time you don't need to send a device Id to server along with your Registration Id as per was in C2DM.
In C2DM every time you request a registration id you will get a new ID.
But in GCM RegId generated by using your application package along with some device id so if you will request for Registration Id again and again you will receive the same RegId.
And if you uninstall an application and will install it again still GCM server will give you the same Registration Id.
So one Registraion Id will do no need of any Device Id to send to server.
I have been successful at migrating from C2DM to GCM. I have also documented how to implement GCM at
http://android.amolgupta.in/2012/07/google-cloud-messaging-gcm-tutorial.html
GCMRegistrar is just a helper that does the leg work described in the first page.
You can see the class here. android-sdk\extras\google\gcm\gcmclient\src\com\google\android\gcm.