我正在使用 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/