Windows Phone 推送通知的第四类推送

时间:2023-03-08 17:48:08

在 MSDN 文档有关 Windows Phone 推送通知 有关推送的内容包含 Tile、Toast、Raw 这三种通知。这三种通知

的方式类似,运用的场合不同,这里不再赘述,它们的运行原理类似:

Windows Phone 推送通知的第四类推送

其实 有关 VoIP 的文档 中,有一个叫 VoipHttpIncomingCallTask 的后台代理:在推送通知通道收到新的传入呼叫时启动,

它使 Windows Phone 运行时 程序集了解到它应该创建新的呼叫。

据说 VoIP 的推送通知是属于强推送的,可靠性比常用的三种要强一些,有关这几种推送的可靠性我没有测试过,不知道是不是

因为微软给 VoIP 的推送通知所使用服务器更可靠一些,纯属猜测。不过像 Skype 这种使用 VoIP 的网络电话,对于呼叫的响应

确实需要比较可靠的消息通知。

在这个代理运行的时候,可以在代理的 OnInvoke(ScheduledTask task) 方法中通过:

VoipHttpIncomingCallTask incomingCallTask = task as VoipHttpIncomingCallTask;
if (incomingCallTask != null)
{
// 把推送通知中自定义的 xml 文本转换成该对象
Notification pushNotification;
using (MemoryStream ms = new MemoryStream(incomingCallTask.MessageBody))
{
XmlSerializer xs = new XmlSerializer(typeof(Notification));
pushNotification = (Notification)xs.Deserialize(ms);//反序列化消息的 xml
}
}

中的 incomingCallTask.MessageBody 获取推送的消息内容,并且这个消息体是可以自定义的,然后可以作为 Toast 或者 Tile 通知显示给用户。作为消息通知,有关 VoIP 的其它的步骤就不再继续了。

MSDN 上介绍的 Toast 和 Tile 通知的步骤:

1)  首先,客户端 app 需要向微软的推送服务器申请 push channel,微软服务器返回 channel 后,客户端把这个 channel 发送到第三方服务器

2)  当第三方服务器需要向用户推送消息时,第三方服务器只需向微软的推送服务器发送 http 数据请求,然后微软的推送服务器再把消息推送到客户端

VoIP 的呼入推送和上面的步骤类似,不同的是客户端代码工程需要创建并添加一个 VoipHttpIncomingCallTask 代理:

// 创建一个新的呼叫代理
VoipHttpIncomingCallTask incomingCallTask = new VoipHttpIncomingCallTask(代理名称, 推送通道);
incomingCallTask.Description = "Incoming call task";
ScheduledActionService.Add(incomingCallTask);

当客户端收到 VoIP 的推送通知时,会调用代理的  OnInvoke(ScheduledTask task) 方法,就可以在这个方法里面获取上面所说的 incomingCallTask.MessageBody 属性中获取消息通知了。

第三方服务器端在发送消息的时候,HttpWebRequest 对象发送 http 请求时,请求报文头:

1) tile 通知:Request.Headers.Add("X-NotificationClass", "1");

2) toast 通知:Request.Headers.Add("X-NotificationClass", "2");

3) raw 通知:Request.Headers.Add("X-NotificationClass", "3");

4) voip 通知:Request.Headers.Add("X-NotificationClass", "4")

并且 VoIP 的消息的报文体可以是自定义的,例如:

Text = @"<?xml version=""1.0"" encoding=""utf-8""?>
<wp:Notification xmlns:wp=""WPNotification"">
<TitleField>北京*</TitleField>
<BodyField>叙利亚可能遭受美国武力打击</BodyField>
</wp:Notification>";

客户端定义相同的字段,用于反序列化:

    public partial class Notification
{
public string TitleField
{ get; set; }
public string BodyField
{ get; set; }
}

客户端和服务器端制定相同的可序列化数据类型进行通信。

这篇文章介绍的工程运行交互大概是:

Windows Phone 推送通知的第四类推送

Windows Phone 推送通知的第四类推送

Windows Phone 推送通知的第四类推送

使用 VoIP 中的 VoipHttpIncomingCallTask 代理进行消息推送时,可以同时显示 Toast 或者 Tile 通知,另一个

好处是,在之前的 Tile通知中,只能更新app的主瓷贴,而次级瓷贴没办法显示通知,使用这个代理时,可以自定义

更新次级瓷贴的消息。

第一步,新建一个wpf 的 pc 端程序,作为推送通知的服务器,运行截图如上面3、中显示的。

页面中的 xaml:

        <TextBox x:Name="txtPushChannel" HorizontalAlignment="Left" Height="80" Margin="109,22,0,0" 
TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="334"/>
<TextBlock HorizontalAlignment="Left" Margin="42,22,0,0" TextWrapping="Wrap" Text="Channel:"
VerticalAlignment="Top"/>
<TextBox x:Name="txtXml" HorizontalAlignment="Left" Height="107" Margin="109,149,0,0" TextWrapping="Wrap"
Text="TextBox" VerticalAlignment="Top" Width="334"/>
<TextBlock HorizontalAlignment="Left" Margin="58,149,0,0" TextWrapping="Wrap" Text="xml:"
VerticalAlignment="Top" RenderTransformOrigin="0.283,0.387"/>
<Button Content="Push" HorizontalAlignment="Left" Margin="205,272,0,0" VerticalAlignment="Top"
Width="75" Click="Button_Click"/>

相应的 C#,作用是向微软的推送服务器发送推送通知,微软的服务器就会把消息推送到注册了该 channel 的手机上面:

    public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
txtXml.Text = @"<?xml version=""1.0"" encoding=""utf-8""?>
<wp:Notification xmlns:wp=""WPNotification"">
<TitleField>北京*</TitleField>
<BodyField>叙利亚可能遭受美国武力打击</BodyField>
</wp:Notification>"; txtPushChannel.Text = "http://db3.notify.live.net/throttledthirdparty/01.00/AQEcdarEW-cRQIsjVBGEMoHVAgAAAAAD
tA8DAAQUZm52OjI0MjhFRkVEMUVERTE0MzAFBkxFR0FDWQ";//推送通道
} private void Button_Click(object sender, RoutedEventArgs e)
{
// 向这个 channel URI 发送推送通知,模拟 VoIP 呼叫
try
{
// 只能使用 HTTP POST 方式向微软的推送服务器发送通知
HttpWebRequest sendNotificationRequest = (HttpWebRequest)WebRequest.Create(txtPushChannel.Text);
sendNotificationRequest.Method = "POST"; // 自定义消息
byte[] notificationMessage = Encoding.UTF8.GetBytes(txtXml.Text); // 设置请求报文头
sendNotificationRequest.ContentLength = notificationMessage.Length;
sendNotificationRequest.ContentType = "text/xml";
sendNotificationRequest.Headers["X-NotificationClass"] = ""; // Class 4 indicates an incoming VoIP call // 发送请求报文体
sendNotificationRequest.BeginGetRequestStream((IAsyncResult arRequest) =>
{
try
{
using (Stream requestStream = sendNotificationRequest.EndGetRequestStream(arRequest))
{
requestStream.Write(notificationMessage, , notificationMessage.Length);
} // 获取微软服务器的响应
sendNotificationRequest.BeginGetResponse((IAsyncResult arResponse) =>
{
try
{
HttpWebResponse response = (HttpWebResponse)sendNotificationRequest.EndGetResponse(arResponse);
string notificationStatus = response.Headers["X-NotificationStatus"];
string subscriptionStatus = response.Headers["X-SubscriptionStatus"];
string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"]; // 显示微软服务器返回的发送状态
this.ShowResult(string.Format("Notification: {0}\r\nSubscription: {1}\r\nDevice: {2}",
notificationStatus, subscriptionStatus, deviceConnectionStatus));
}
catch (Exception ex)
{
this.ShowResult(ex);
}
}, null);
}
catch (Exception ex)
{
this.ShowResult(ex);
}
}, null);
}
catch (Exception ex)
{
this.ShowResult(ex);
}
} // 显示弹出框消息
private void ShowResult(object result)
{
this.Dispatcher.BeginInvoke((Action)(() =>
{
Exception ex = result as Exception;
if (ex == null)
{
MessageBox.Show(string.Format("{0}\r\n{1}", result, DateTime.Now));
}
else
{
MessageBox.Show(string.Format("An error has occurred\r\n{0}", DateTime.Now));
}
}), null);
}
}

第二步,新建一个 WP 客户端程序,默认名称 PhoneApp1,运行截图如上面 1、中所示。

新建一个 VoipHttpIncomingCallTask 后台代理工程,作用是 接收传入呼叫通知时启动该后台代理,

然后显示 Toast 通知和 Tile 通知:

Windows Phone 推送通知的第四类推送

该代理的全部代码:

   public class ScheduledAgent : ScheduledTaskAgent
{
/// <remarks>
/// ScheduledAgent 构造函数,初始化 UnhandledException 处理程序
/// </remarks>
static ScheduledAgent()
{
// 订阅托管的异常处理程序
Deployment.Current.Dispatcher.BeginInvoke(delegate
{
Application.Current.UnhandledException += UnhandledException;
});
} /// 出现未处理的异常时执行的代码
private static void UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
if (Debugger.IsAttached)
{
// 出现未处理的异常;强行进入调试器
Debugger.Break();
}
} /// <summary>
/// 运行计划任务的代理
/// </summary>
/// <param name="task">
/// 调用的任务
/// </param>
/// <remarks>
/// 调用定期或资源密集型任务时调用此方法
/// </remarks>
protected override void OnInvoke(ScheduledTask task)
{
//TODO: 添加用于在后台执行任务的代码
VoipHttpIncomingCallTask incomingCallTask = task as VoipHttpIncomingCallTask;
if (incomingCallTask != null)
{ // 把推送通知中自定义的 xml 文本转换成该对象
Notification pushNotification;
using (MemoryStream ms = new MemoryStream(incomingCallTask.MessageBody))
{
XmlSerializer xs = new XmlSerializer(typeof(Notification));
pushNotification = (Notification)xs.Deserialize(ms);//反序列化消息的 xml
} Debug.WriteLine(" Incoming call from caller {0}, number {1}", pushNotification.TitleField, pushNotification.BodyField); // 显示通知
ChangeMainTile(pushNotification.TitleField, pushNotification.BodyField);
}
NotifyComplete();//通知操作系统,代理已针对代理的当前调用完成其目标任务。
} // 同时显示 Tile 通知和 Toast 通知(app的主瓷贴需要订到开始菜单中)
void ChangeMainTile(string strTitle, string strBody)
{
// Toast 通知
ShellToast to = new ShellToast();
to.Title = strTitle;
to.Content = strBody; to.Show(); // 瓷贴通知,第一个瓷贴为 app 的主瓷贴
ShellTile defaultTile = ShellTile.ActiveTiles.First();
StandardTileData tileData = new StandardTileData()
{
Title = strTitle,
Count = ,
BackTitle = strTitle,
BackContent = strBody
}; defaultTile.Update(tileData);
}
}
    //自定义消息的字段
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17613")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "WPNotification")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "WPNotification", IsNullable = false)]
public partial class Notification
{ private string titleField; private string bodyField; //消息标题
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string TitleField
{
get
{
return this.titleField;
}
set
{
this.titleField = value;
}
} //消息体
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string BodyField
{
get
{
return this.bodyField;
}
set
{
this.bodyField = value;
}
}
}

第三步,把第二步中的代理添加引用到 PhoneApp1 工程中:

Windows Phone 推送通知的第四类推送

在 MainPage 中添加一个文本框和一个按钮:

  <TextBox x:Name="txtRegist" HorizontalAlignment="Left" Height="318" Margin="23,125,-23,0" 
TextWrapping="Wrap" Text="channel" VerticalAlignment="Top" Width="456"/>
<Button Content="Regist" HorizontalAlignment="Left" Margin="113,495,0,0"
VerticalAlignment="Top" Width="265" Click="Button_Click"/>

在 MainPage 的 codebehind 页面中,添加注册 push channel 逻辑:

 public partial class MainPage : PhoneApplicationPage
{
// 构造函数
public MainPage()
{
InitializeComponent();
} // VoIP 的呼入任务的名称
static string incomingCallTaskName = "PhoneVoIPApp.IncomingCallTask";
// 推送通知的通道名称
static string pushChannelName = "VoIPChannel";
private void Button_Click(object sender, RoutedEventArgs e)
{
InitPushChannel();
} private void InitPushChannel()
{
// 查找以前注册的推送通知通道
HttpNotificationChannel httpChannel = HttpNotificationChannel.Find(pushChannelName); // 如果以前没有注册通道,则创建一个新通道
if (httpChannel == null || httpChannel.ChannelUri == null)
{
httpChannel = new HttpNotificationChannel(pushChannelName);
httpChannel.Open();
}
else
{
// 已经存在的推送通道
pushChannelName = httpChannel.ChannelUri.ToString();
Debug.WriteLine("[App] Existing Push channel URI is {0}", pushChannelName);
} // 注册相应的事件
httpChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(PushChannel_ChannelUriUpdated);
httpChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(PushChannel_ErrorOccurred);
httpChannel.HttpNotificationReceived += new EventHandler<HttpNotificationEventArgs>(PushChannel_HttpNotificationReceived);
} private void PushChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
{
Debug.WriteLine("[App] New Push channel URI is {0}", e.ChannelUri); InitHttpNotificationTask(); // 初始化 VoIP 呼入通知的代理 this.Dispatcher.BeginInvoke(delegate { txtRegist.Text = e.ChannelUri + ""; }); // TODO: Let your cloud server know that the push channel to this device is e.ChannelUri.
} private void PushChannel_ErrorOccurred(object sender, NotificationChannelErrorEventArgs e)
{
// TODO: Let your cloud server know that the push channel to this device is no longer valid.
} private void PushChannel_HttpNotificationReceived(object sender, HttpNotificationEventArgs e)
{
// TODO: Process raw push notifications here, if required.
} public void InitHttpNotificationTask()
{
// 获取已经存在的 VoIP 呼入代理
VoipHttpIncomingCallTask incomingCallTask = ScheduledActionService.Find(incomingCallTaskName) as VoipHttpIncomingCallTask;
if (incomingCallTask != null)
{
if (incomingCallTask.IsScheduled == false)
{
// 从计划操作服务中删除具有指定名称的 ScheduledAction
ScheduledActionService.Remove(incomingCallTaskName);
}
else
{
// 计划任务已经添加,并且计划状态为 true,则直接返回
return;
}
} // 创建一个新的呼叫代理
incomingCallTask = new VoipHttpIncomingCallTask(incomingCallTaskName, pushChannelName);
incomingCallTask.Description = "Incoming call task";
ScheduledActionService.Add(incomingCallTask);
}
}

代码工程完成,源码下载

这个工程是参考微软的VoIP代码完成的,具体关于 VoIP 的代码: ChatterBox VoIP sample app