谷歌云消息“未注册”失败并取消订阅最佳做法?
Posted
技术标签:
【中文标题】谷歌云消息“未注册”失败并取消订阅最佳做法?【英文标题】:Google cloud message 'Not Registered' failure and unsubscribe best practices? 【发布时间】:2016-04-03 11:43:59 【问题描述】:我正在使用 Xamarin Forms 开发一个 android 应用程序,其主要目的是接收事件的推送通知。在设备成功调用GcmPubSub.getInstance().subscribe()
后,我在发送通知时遇到了一些看似随机的问题,接收Not Registered
失败。这发生在一两周前,我认为我通过始终使用主应用程序上下文生成令牌和getInstance()
调用解决了这个问题。
昨天美国东部标准时间中午左右,问题再次出现,然后在 4:00 - 4:30 左右突然开始工作。下午充满了注释代码以简化事情和其他随机事情,例如删除和重新添加 NuGet 包。现在我回到昨天停止工作之前的代码,一切都像蛤蜊一样快乐。
当这个问题发生时,只有当subscribe()
呼叫是通过wifi 进行时。如果我在蜂窝网络上调试手机上的应用程序,我永远不会收到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(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());
【问题讨论】:
这可能与this 重复。GcmRegistrationService.GetIntent(this, NotificationTopic)
可能指向与 InstanceID 和 GcmPubSub 不同的上下文。
很好,尽管几周前我经历了这个,发现我需要使用主应用程序上下文。抱歉,我应该更明确地引用它。
本文档可能会为正确的unsubscription 进程提供一些启示。
下次发生这种情况(如果再次发生)我将尝试显式卸载。我很确定我在昨天的实验中这样做了,但听起来开发部署可能会触发类似卸载的场景,其中 GCM 服务器正在标记要删除的令牌
再想一想,我将跟踪我在服务器上收到Not Registered
响应的令牌,如果再次尝试使用该令牌,我将尝试响应-新令牌消息。我们会看到它是如何飞行的
【参考方案1】:
所以是的,令牌的更改似乎确实解决了我的问题。当我测试新逻辑时,我遇到了一个场景,其中 InstanceId 想要使用的令牌返回为Not Registered
。在我删除了 InstanceId 并重新生成了一个新令牌后,我成功地向设备发送了一条消息。
作为旁注,我还从注销逻辑中删除了unsubscribe()
调用。感谢@gerardnimo 的链接
为了实现这一点,我创建了一个删除令牌和 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);
【讨论】:
遇到了同样的问题。删除 instanceID/DeleteToken 也对我有用。这是 Xamarin 调试问题!?这会在生产中发生吗!? 我认为这只是一个调试问题,在部署到 VM 或设备时似乎被“卸载”(至少这是我的怀疑) 是的,经过进一步测试,当您更改 AndroidManifest 并部署时会发生这种情况。以前的应用程序已“卸载”并且令牌无效。我在生产中进行了测试,在那里我推出了更新以查看旧版本是否也被“卸载”,但事实并非如此。仅在 Xamarin 调试期间发生。经过这么多问题,我们正式放弃了 Xamarin,它们弊大于利,也浪费时间。 似乎是我的问题,在部署之间卸载并重新安装应用程序!在我的情况下,我在尝试订阅主题时收到了 INVALID_PARAMETERS(虽然是间歇性的)【参考方案2】:我自己也遇到了同样的 Xamarin 问题,显然,当您使用 Xamarin Studio 进行部署时,它会以某种方式替换 APK,而不会触发 GetToken() 生成新令牌所需的任何操作。
正确的解决方案是检测是否发生了 Xamarin Studio 部署并使用 DeleteInstanceID() 强制刷新令牌。
我想出的最接近的方法是检测 APK 是否已被替换(通过定期更新或 Xamarin Studio 部署)并仅在这些情况下强制刷新令牌。
private bool IsForcedTokenRefreshNeeded()
DateTime actualWriteDT = GetAPKLastWriteDT();
DateTime? storedLastWriteDT = RetrieveAPKLastWriteDT();
bool forceTokenRefresh = false;
if (storedLastWriteDT.HasValue)
if (actualWriteDT != storedLastWriteDT)
forceTokenRefresh = true;
StoreAPKLastWriteDT(actualWriteDT);
else
StoreAPKLastWriteDT(actualWriteDT);
return forceTokenRefresh;
private void StoreAPKLastWriteDT(DateTime lastWriteDT)
var prefs = Application.Context.GetSharedPreferences("MyApp", FileCreationMode.Private);
var prefEditor = prefs.Edit();
prefEditor.PutLong("APKLastModified", lastWriteDT.Ticks);
prefEditor.Commit();
private DateTime? RetrieveAPKLastWriteDT()
//retreive
var prefs = Application.Context.GetSharedPreferences("MyApp", FileCreationMode.Private);
long value = prefs.GetLong("APKLastModified", 0);
if (value == 0)
return null;
return new DateTime(value);
private DateTime GetAPKLastWriteDT()
string packageName = Android.App.Application.Context.PackageName;
Android.Content.PM.ApplicationInfo appInfo = this.PackageManager.GetApplicationInfo(packageName, 0);
string appFile = appInfo.SourceDir;
return new FileInfo(appFile).LastWriteTimeUtc;
以及主要的 GcmRegistrationService 方法:
protected override void OnHandleIntent (Intent intent)
Log.Info("GcmRegistrationService", "Calling InstanceID.GetToken");
string token;
bool forceTokenRefresh = IsForcedTokenRefreshNeeded();
try
lock (m_lock)
InstanceID instanceID = InstanceID.GetInstance (Android.App.Application.Context);
if (forceTokenRefresh)
Log.Info("GcmRegistrationService", "Forced token refresh");
instanceID.DeleteInstanceID();
token = instanceID.GetToken(SenderID, GoogleCloudMessaging.InstanceIdScope, null);
Log.Info("GcmRegistrationService", "GCM Registration Token: " + token);
catch (Exception ex)
Log.Debug("GcmRegistrationService", "Failed to get a registration token: " + ex.Message);
return;
try
SendRegistrationToAppServer(token);
catch(WebException)
if (forceTokenRefresh)
// this will force a refresh next time
StoreAPKLastWriteDT(DateTime.MinValue);
try
Subscribe(token);
catch (Exception ex)
Log.Debug("GcmRegistrationService", "Failed to subscribe: " + ex.Message);
return;
【讨论】:
以上是关于谷歌云消息“未注册”失败并取消订阅最佳做法?的主要内容,如果未能解决你的问题,请参考以下文章