android - 谷歌云消息 'Not Registered'失败和退订最佳实践?

标签 android xamarin google-cloud-messaging xamarin.forms

我正在使用 Xamarin Forms 开发一个 Android 应用程序,其主要目的是接收事件的推送通知。在设备成功调用 GcmPubSub.getInstance().subscribe() 后,我在发送通知时遇到了一些看似随机的问题,收到 Not Registered 失败。这发生在一两周前,我认为通过始终使用主应用程序上下文生成 token 和调用 getInstance() 可以解决问题。

昨天美国东部标准时间中午左右,问题再次出现,然后在 4:00 - 4:30 左右突然开始工作。下午充满了注释代码以简化事情和其他随机事情,例如删除和重新添加 NuGet 包。现在我回到了昨天它停止工作之前的代码,一切都很顺利。

只有在通过 wifi 调用 subscribe() 时才会出现此问题。如果我在蜂窝网络上调试手机上的应用程序,我永远不会收到 Not Registered 故障。

当用户在应用程序中注销时,我目前正在调用 unsubscribe(),并且我已经能够成功取消订阅和重新订阅(今天早上)。

当通知是特定于用户的时,取消订阅注销是推送通知的最佳做法吗?我认为这可能会使 GCM 服务器在某种程度上感到困惑。 p>

关于为什么我可能会收到 Not Registered 失败的任何建议也很棒。

注册(订阅/退订)服务:

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);
            }
        }
    }
}

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>

主要 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;
        }
    }

}

服务器端发送通知。 Device.CloudMessageToken 由上述注册服务中的 DeviceService.UpdateCloudMessageToken( token ) 调用填充:

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 contentValues = JsonConvert.DeserializeObject<Dictionary<string, object>>(content);

        if ((long)contentValues["failure"] == 1)
        {
            var results = (JArray)contentValues["results"];
            throw new Exception(results[0]["error"].ToString());
        }

    }

}

最佳答案

是的,更改 token 似乎确实解决了我的问题。当我测试新逻辑时,我遇到了一个场景,其中 InstanceId 想要使用返回为 Not Registered 的 token 。在删除 InstanceId 并重新生成新 token 后,我能够成功地向设备发送消息。

作为旁注,我还从注销逻辑中删除了 unsubscribe() 调用。感谢@gerardnimo 的链接

为了完成这个,我创建了一个删除 token 和 InstanceId 的新服务(虽然我可能只需要删除 InstanceId),然后调用 GcmRegistrationService

/// <summary>
/// Gcm reregistration service to delete and recreate the token.
/// </summary>
[Service(Exported = false)]
public class GcmReregistrationService : IntentService
{

    private static readonly object Locker = new object();

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

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

        valuesForActivity.PutString ("token", token);
        valuesForActivity.PutString ("topic", topic);

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

        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 instanceId = InstanceID.GetInstance(Forms.Context);
        instanceId.DeleteToken (token, GoogleCloudMessaging.InstanceIdScope);
        instanceId.DeleteInstanceID ();

        var subscribeIntent = GcmRegistrationService.GetIntent(Forms.Context, topic);
        Forms.Context.StartService(subscribeIntent);

    }
}

关于android - 谷歌云消息 'Not Registered'失败和退订最佳实践?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34513614/

相关文章:

android - "This app isn' t 兼容你的手机"Android 7.0

安卓 GCM : Problems storing registrationIDs on Database

android - 对于GcmListenerService 是onMessageReceived 后台吗?

android - 尝试在空对象引用(Fragment)上调用虚拟方法 'android.content.res.Resources$Theme android.content.Context.getTheme()'

android - 是否可以在 Android 和 iOS 中使用代码来完成所有用户界面的工作

android - 如何阻止 ADT Logcat View 在 Eclipse 中自动弹出?

google-cloud-messaging - GCM - 谷歌云消息 : Connection closed when ccs client logins

android - 可扩展的 ListView 组指示器在获得焦点时自动更改

iOS:应用程序内存被操作系统清除后如何检测 FinishedLaunching

c# - 在 Xamarin 中绑定(bind) iOS 委托(delegate)时出错