Search code examples
c#xamarin.androidgoogle-cloud-messagingpushsharp

GCM Pushing The Same Message Multiple Times (PushSharp)


Problem:

I'm developing an android app with Xamarin that uses PushSharp. I am using GCM to send messages to the clients so I can update certain things if the app is open. GCM seems to be sending the same message to the same device multiple times.


Logcat:

Thread started:  #12
09-17 08:40:34.307 I/PushSharp-GCM(20855): GCM Message Received!
09-17 08:40:34.317 V/GCMBaseIntentService(20855): Releasing Wakelock
09-17 08:40:34.327 V/UpdateSignalReceiver(20855): Message Receieved: *****
Thread finished:  #12
The thread 'Unknown' (0xc) has exited with code 0 (0x0).
09-17 08:40:34.787 V/PushHandlerBroadcastReceiver(20855): OnReceive: com.google.android.c2dm.intent.RECEIVE
09-17 08:40:34.787 V/PushHandlerBroadcastReceiver(20855): GCM IntentService Class: rAMP_TabletV1.x5.GCMIntentService
09-17 08:40:34.787 V/GCMBaseIntentService(20855): Acquiring wakelock
Thread started:  #13
09-17 08:40:34.807 I/PushSharp-GCM(20855): GCM Message Received!
09-17 08:40:34.817 V/GCMBaseIntentService(20855): Releasing Wakelock
09-17 08:40:34.817 V/UpdateSignalReceiver(20855): Message Receieved: *****
Thread finished:  #13
The thread 'Unknown' (0xd) has exited with code 0 (0x0).
09-17 08:40:35.817 V/PushHandlerBroadcastReceiver(20855): OnReceive: com.google.android.c2dm.intent.RECEIVE
09-17 08:40:35.817 V/PushHandlerBroadcastReceiver(20855): GCM IntentService Class: rAMP_TabletV1.x5.GCMIntentService
09-17 08:40:35.817 V/GCMBaseIntentService(20855): Acquiring wakelock
Thread started:  #14
09-17 08:40:35.857 I/PushSharp-GCM(20855): GCM Message Received!
09-17 08:40:35.857 V/GCMBaseIntentService(20855): Releasing Wakelock
09-17 08:40:35.867 V/UpdateSignalReceiver(20855): Message Receieved: *****
Thread finished:  #14
The thread 'Unknown' (0xe) has exited with code 0 (0x0).
09-17 08:40:36.277 V/PushHandlerBroadcastReceiver(20855): OnReceive: com.google.android.c2dm.intent.RECEIVE
09-17 08:40:36.277 V/PushHandlerBroadcastReceiver(20855): GCM IntentService Class: rAMP_TabletV1.x5.GCMIntentService
09-17 08:40:36.277 V/GCMBaseIntentService(20855): Acquiring wakelock
Thread started:  #15
09-17 08:40:36.327 I/PushSharp-GCM(20855): GCM Message Received!
09-17 08:40:36.327 V/GCMBaseIntentService(20855): Releasing Wakelock
09-17 08:40:36.337 V/UpdateSignalReceiver(20855): Message Receieved: *****
Thread finished:  #15
The thread 'Unknown' (0xf) has exited with code 0 (0x0).
09-17 08:40:36.717 V/PushHandlerBroadcastReceiver(20855): OnReceive: com.google.android.c2dm.intent.RECEIVE
09-17 08:40:36.717 V/PushHandlerBroadcastReceiver(20855): GCM IntentService Class: rAMP_TabletV1.x5.GCMIntentService
09-17 08:40:36.717 V/GCMBaseIntentService(20855): Acquiring wakelock
Thread started:  #16
09-17 08:40:36.747 I/PushSharp-GCM(20855): GCM Message Received!
09-17 08:40:36.757 V/GCMBaseIntentService(20855): Releasing Wakelock
09-17 08:40:36.757 V/UpdateSignalReceiver(20855): Message Receieved: *****

Sending the Message:

Each device is registered and the registration id is stored in a database. I check the version number and the previous registration id to make sure that there are no duplicates, and also make sure I have the correct registration id. (I have double checked to make sure that there are no duplicates in my database)

The update is called from a WCF service, and it will send the messages to all the registered devices. The method is only called once, and AllRegisteredDevices is a distinct list of device ids to send the message to.

foreach (var deviceId in AllRegisteredDevices)
{
    var webRequest = WebRequest.Create("https://android.googleapis.com/gcm/send");
    webRequest.Method = "post";
    webRequest.ContentType = " application/x-www-form-urlencoded;charset=UTF-8";
    webRequest.Headers.Add(string.Format("Authorization: key={0}", GoogleAppID));
    webRequest.Headers.Add(string.Format("Sender: id={0}", SENDER_ID));
    var postData = "collapse_key=score_update&time_to_live=108&delay_while_idle=1&data.message="
        + value + "&registration_id=" + deviceId + "";
    Byte[] bytes = Encoding.UTF8.GetBytes(postData);
    webRequest.ContentLength = bytes.Length;
    var dataStream = webRequest.GetRequestStream();
    dataStream.Write(bytes, 0, bytes.Length);
    dataStream.Close();
    var webResponse = webRequest.GetResponse();
    dataStream = webResponse.GetResponseStream();
    var streamReader = new StreamReader(dataStream);
    var responseFromServer = streamReader.ReadToEnd();
    streamReader.Close();
    dataStream.Close();
    webResponse.Close();
} // end loop

Receiving The Message:

I have a custom Broadcast Receiver to handle the message, depending on what the value of "message" is. The PushService passes it along to the receiver.

Push Service

protected override void OnMessage(Context context, Intent intent)
{
     Log.Info(PushHandlerBroadcastReceiver.TAG, "GCM Message Received!");

     string message = intent.Extras.GetString("message");
     var theIntent = new Intent(UpdateAction);
     theIntent.PutExtra("message", message);
     SendOrderedBroadcast(theIntent, null);
} // end OnMessage

UpdateSignalReceiver

[BroadcastReceiver]
[IntentFilter(new string[]{PushHandlerService.UpdateAction}, Priority = (int)IntentFilterPriority.HighPriority)]
public class UpdateSignalReceiver : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {       
        MyActivity TheActivity = ((MyActivity )context);
        string message = intent.Extras.GetString("message") ?? "";
        Log.Verbose("UpdateSignalReceiver", "Message Receieved: " + message);
        if (message == "foo")
        {
            TheActivity.DoSomething();
        } // end if
        else if (message == "bar")
        {
            TheActivity.SomethingElse();
        } // end else if
        else
        {
            TheActivity.CatchAllMethod();
        } // end else
        InvokeAbortBroadcast();
    } // end on receieve
} // end UpdateSignalReceiver

Environment

  • Samsung Galaxy Tab 3
  • Only factory installed apps (no other apps using the same GCM)

Research:


Question:

Why am I receiving the same message repeatedly, when I'm only sending it once?


Solution

  • I have decided to go with a throttling approach. While this is not perfect in its implementation, the general idea is as follows:

    • All Messages are given a Unique ID (Guid)
    • The Broadcast Receiver has a static list of Guids containing the last 20 Message Ids we received and handled
    • Only do work on messages that have an id not contained in our list, so we know it's a new message to this device.
    • Limit the List to 20 IDs so it doesn't grow out of control. (20 seems to be more than enough, as repeat messages typically come in 3, sometimes 5 iterations)

    [BroadcastReceiver]
    [IntentFilter(new string[]{PushHandlerService.UpdateAction}, Priority = (int)IntentFilterPriority.HighPriority)]
    public class UpdateSignalReceiver : BroadcastReceiver
    {
        private static List<Guid> _Last20MessageIds;
    
        public override void OnReceive(Context context, Intent intent)
        {       
            Guid MessageId;
            // Pull the MessageId from the intent
            String MessageIdString = intent.Extras.GetString("message_id" ?? Guid.Empty.ToString());
            Guid.TryParse(MessageIdString, out MessageId);
    
    
            if (_Last20MessageIds == null)
            {
                _Last20MessageIds = new List<Guid>();
            }
    
            // Make sure we didn't already receive this Message, then do work
            if (MessageId != null && MessageId != Guid.Empty && ! _Last20MessageIds.Contains(MessageId))
            {
                DoSomeWorkWithIntent(intent);
    
    
                // Add the guid to the message id list
                _Last20MessageIds.Insert(0, MessageId);
    
                // Trim the list to the most recent 20
                _Last20MessageIds= _Last20MessageIds.Take(20).ToList();
            }
            InvokeAbortBroadcast();
        } // end on receive
    } // end UpdateSignalReceiver