Android GCM push notifications registration done right
Re-register GCM push notifications on app upgrade with MY_PACKAGE_REPLACED
When implementing push notifications using GCM (Google Cloud Messaging) on Android, one of the gotchas of which to be aware is the “application update” scenario. The Google documentation states:
When an application is updated, it should invalidate its existing registration ID, as it is not guaranteed to work with the new version. Because there is no lifecycle method called when the application is updated, the best way to achieve this validation is by storing the current application version when a registration ID is stored. Then when the application is started, compare the stored value with the current application version. If they do not match, invalidate the stored data and start the registration process again.
What the documentation doesn’t mention is that as of API 12 you can use a Broadcast intent action that allows you to react in case your app has been updated:
ACTION_MY_PACKAGE_REPLACED - Broadcast Action: A new version of your application has been installed over an existing one.
This should allow us to re-register with GCM in a more elegant, and less error-prone way.
UPDATE: Another reason I rather use this Intent instead of Google’s approach is because their sample solution relies on the app being launched before attempting to re-register. This could potentially lead to users being left without push notifications until they manually open the app. With ACTION_MY_PACKAGE_REPLACED the code logic executes on the background immediately after the app is updated (which also takes advantage of the device having internet connection).
Steps:
1. Register a new BroadcastReceiver that will intercept an app-update action in your AndroidManifest.xml:
<receiver android:name=".PackageReplacedReceiver">
<!— Useful for detecting when the application is upgraded so we can request a new GCM ID -->
<!— MY_PACKAGE_REPLACED only alerts for this same app, and it is only available on API 12 and up -->
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
2. Use a (wakeful) BroadcastReceiver to receive the the Intent, and then trigger a GCM re-registration:
public class PackageReplacedReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null && intent.getAction().equals(Intent.ACTION_MY_PACKAGE_REPLACED)) {
// invalidate your current GCM registration id, and re-register with GCM server using an IntentService
Intent startServiceIntent = new Intent(context, GcmPushRegistrationService.class);
GcmPushUpgradeReceiver.startWakefulService(context, startServiceIntent);
}
}
}
3. (This implementation is up to you) Use an IntentService to perform the GCM re-registration in the background:
public class GcmPushRegistrationService extends IntentService {
@Override
public void handleIntent(Intent intent) {
// Remove the stored GCM registration ID
clearGcmRegistrationId();
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
String regId = gcm.register(getGcmSenderId());
// You should send the registration ID to your server over HTTP,
// so it can use GCM/HTTP or CCS to send messages to your app.
// The request to your server should be authenticated if your app
// is using accounts.
sendRegistrationIdToBackend(regId);
// store the regId locally somewhere (e.g. SharedPreferences)
storeGcmRegistrationId(regId);
// Release the wake lock provided by the WakefulBroadcastReceiver.
GcmPushUpgradeReceiver.completeWakefulIntent(intent);
}
}
And that should be it! This will ensure your app automatically re-registers every time it gets updated from the Play Store. You should still check whether your app is registered on every app launch though.
- In case you’re using an older API (less than 12) the PACKAGE_REPLACED action is also available, which behaves the same way but launching your receiver every time any app gets updated. In order to react only to your app update you need to verify the Intent’s data:
if(intent.getData().getSchemeSpecificPart().equals(context.getPackageName()))