Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
192 views
in Technique[技术] by (71.8m points)

android - Google cloud message 'Not Registered' failure and unsubscribe best practices?

I'm developing an Android app using Xamarin Forms that's primary purpose is receiving push notifications of events. I've had some seemingly random trouble receiving Not Registered failures when sending a notification, after the device successfully calls GcmPubSub.getInstance().subscribe(). This was happening a week or 2 ago and I thought I had the problem resolved by always using the main application context for token generation and the getInstance() call.

Yesterday around noon EST the problem reoccurred and then just as suddenly started working around 4:00 - 4:30. Afternoon was full of commenting code to simplify things and other random things like removing and re-adding NuGet packages. Now I'm back to the code that I had in place just before it stopped working yesterday and everything is happy as a clam.

When this problem happens it's only when the subscribe() call is made over wifi. If I debug the app on my phone on the cellular network I never receive the Not Registered failure.

I'm currently calling unsubscribe() when the user logs off in the app, and I've been able to unsubscribe and re-subscribe successfully (this morning).

Is unsubscribing on log off a best practice for push notifications when the notifications are user specific? I thought that there may be a possibility that this was making the GCM servers confused in some way.

Any suggestions on why I might be receiving the Not Registered failures would be awesome too.

The registration (subscribe/unsubscribe) services:

namespace MyApp.Droid.Services
{
    /// <summary>
    /// The background process that handles retrieving GCM token
    /// </summary>
    [Service(Exported = true)]
    public class GcmRegistrationService : IntentService
    {
        private static readonly object Locker = new object();

        public GcmRegistrationService() : base("GcmRegistrationService") { }

        public static Intent GetIntent(Context context, string topic)
        {
            var valuesForActivity = new Bundle();
            valuesForActivity.PutString("topic", topic);

            var intent = new Intent(context, typeof(GcmRegistrationService));

            intent.PutExtras(valuesForActivity);

            return intent;
        }

        protected override async void OnHandleIntent(Intent intent)
        {
            try
            {
                // Get the count value passed to us from MainActivity:
                var topic = intent.Extras.GetString("topic", "");

                if (string.IsNullOrWhiteSpace(topic))
                    throw new Java.Lang.Exception("Missing topic value");

                string token;

                Log.Info("RegistrationIntentService", "Calling InstanceID.GetToken");
                lock (Locker)
                {
                    var instanceId = InstanceID.GetInstance(Forms.Context);
                    var projectNumber = Resources.GetString(Resource.String.ProjectNumber);
                    token = instanceId.GetToken(projectNumber, GoogleCloudMessaging.InstanceIdScope, null);

                    Log.Info("RegistrationIntentService", "GCM Registration Token: " + token);

                    Subscribe(token, topic);
                }

                var applicationState = ApplicationStateService.GetApplicationState ();

                // Save the token to the server if the user is logged in
                if(applicationState.IsAuthenticated)
                    await SendRegistrationToAppServer(token);
            }
            catch (SecurityException e)
            {
                Log.Debug("RegistrationIntentService", "Failed to get a registration token because of a security exception");
                Log.Debug ("RegistrationIntentService", "Exception message: " + e.Message);
                //ToastHelper.ShowStatus("Google Cloud Messaging Security Error");
                throw;
            }
            catch (Java.Lang.Exception e)
            {
                Log.Debug("RegistrationIntentService", "Failed to get a registration token");
                Log.Debug ("RegistrationIntentService", "Exception message: " + e.Message);
                //ToastHelper.ShowStatus("Google Cloud Messaging Error");
                throw;
            }

        }

        private async System.Threading.Tasks.Task SendRegistrationToAppServer(string token)
        {
            // Save the Auth Token on the server so messages can be pushed to the device
            await DeviceService.UpdateCloudMessageToken (token);

        }

        void Subscribe(string token, string topic)
        {

            var pubSub = GcmPubSub.GetInstance(Forms.Context);

            pubSub.Subscribe(token, "/topics/" + topic, null);
            Log.Debug("RegistrationIntentService", "Successfully subscribed to /topics/" +topic);
            ApplicationStateService.SaveCloudMessageToken(token, topic);
        }

    }


    /// <summary>
    /// The background process that handles unsubscribing GCM token
    /// </summary>
    [Service(Exported = false)]
    public class GcmUnsubscribeService : IntentService
    {

        private static readonly object Locker = new object();

        public GcmUnsubscribeService() : base("GcmUnsubscribeService") { }

        public static Intent GetIntent(Context context, ApplicationState applicationState, bool resubscribe=false)
        {
            var valuesForActivity = new Bundle();

            valuesForActivity.PutString ("token", applicationState.CloudMessageToken);
            valuesForActivity.PutString ("topic", applicationState.Topic);
            valuesForActivity.PutBoolean ("resubscribe", resubscribe);

            var intent = new Intent(context, typeof(GcmUnsubscribeService));

            intent.PutExtras(valuesForActivity);

            return intent;
        }

        protected override void OnHandleIntent(Intent intent)
        {

            // Get the count value passed to us from MainActivity:
            var token = intent.Extras.GetString("token", "");
            var topic = intent.Extras.GetString("topic", "");
            var resubscribe = intent.Extras.GetBoolean ("resubscribe");

            var pubSub = GcmPubSub.GetInstance(Forms.Context);
            try
            {
                pubSub.Unsubscribe (token, "/topics/" + topic);
            }
            catch(IOException e) 
            {
                var x = e.Message;
            }

            if (resubscribe) {
                var subscribeIntent = GcmRegistrationService.GetIntent(Forms.Context, topic);
                Forms.Context.StartService(subscribeIntent);
            }
        }
    }
}

The AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:installLocation="auto" 
    package="com.me.notification_app" 
    android:versionCode="1" 
    android:versionName="1.0">

    <uses-sdk android:minSdkVersion="19" />

    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <permission 
        android:name="com.me.notification_app.permission.C2D_MESSAGE" 
        android:protectionLevel="signature" />

    <uses-permission 
        android:name="com.me.notification_app.permission.C2D_MESSAGE" />

    <application 
        android:label="Notification App" 
        android:icon="@drawable/icon">

        <receiver 
            android:name="com.google.android.gms.gcm.GcmReceiver" 
            android:permission="com.google.android.c2dm.permission.SEND"
            android:exported="true">

            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="com.me.notification_app" />
            </intent-filter>

        </receiver>

    </application>
</manifest>

The main activity:

[Activity(Label = "MyApp", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
{
    public static string NotificationTopic = "MyEvent";

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        global::Xamarin.Forms.Forms.Init(this, bundle);
        LoadApplication(new App(DeviceType.Android));

        if (IsPlayServicesAvailable())
        {
            var intent = GcmRegistrationService.GetIntent(this, NotificationTopic);
            StartService(intent);
        }
    }


    public bool IsPlayServicesAvailable()
    {
        var resultCode = GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(this);
        if (resultCode != ConnectionResult.Success)
        {
            if (GoogleApiAvailability.Instance.IsUserResolvableError(resultCode))
                ToastHelper.ShowStatus("Google Play Services error: " + GoogleApiAvailability.Instance.GetErrorString(resultCode));
            else
            {
                ToastHelper.ShowStatus("Sorry, notifications are not supported");
            }
            return false;
        }
        else
        {                
            return true;
        }
    }

}

The server side sending of a notification. The Device.CloudMessageToken is populated by the DeviceService.UpdateCloudMessageToken (token) call in the registration service above:

public async Task SendNotificationAsync(Device device, string message, Dictionary<string, string> extraData = null)
{
    if (string.IsNullOrWhiteSpace(device.CloudMessageToken))
        throw new Exception("Device is missing a CloudMessageToken");

    var apiKey = _appSettingsHelper.GetValue("GoogleApiKey");
    var gcmBaseUrl = _appSettingsHelper.GetValue("GoogleCloudMessageBaseUrl");
    var gcmSendPath = _appSettingsHelper.GetValue("GoogleCloudMessageSendPath");

    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(gcmBaseUrl);
        client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "key=" + apiKey);
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));


        var messageInfo = new MessageInfo
        {
            to = device.CloudMessageToken,
            data = new Dictionary<string, string>
            {
                {"message", message}
            }
        };

        if (extraData != null)
        {
            foreach (var data in extraData)
            {
                messageInfo.data.Add(data.Key, data.Value);
            }
        }

        var messageInfoJson = JsonConvert.SerializeObject(messageInfo);

        var response =
            await
                client.PostAsync(gcmSendPath,
                    new StringContent(messageInfoJson, Encoding.UTF8, "application/json"));

        response.EnsureSuccessStatusCode();

        var content = await response.Content.ReadAsStringAsync();

        var cont

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...